i want to get a cursor position from UITextview, i have implement the code in How to find a pixel-positon of a cursor in UITextView? post, the algorithm is based on algorithm in Pixel-Position of Cursor in UITextView.
my problem is, i can't get the selectedRange.location value after keyboard appear in the 2nd time, cause it always tell that the value of location is 2147483647, which means not found. This is my implementation :
-(void)getCursorPosition{
NSRange originalPosition = self.codeTextView.selectedRange;
NSLog(#"original position : %d", self.codeTextView.selectedRange.location);
CGPoint origin = self.codeTextView.frame.origin;
unichar c = ' ';
NSLog(#"location : %d", self.codeTextView.selectedRange.location);
NSLog(#"length : %d", self.codeTextView.selectedRange.length);
if (self.codeTextView.selectedRange.location != [self.codeTextView.text length]) {
// NSLog(#"cek 1");
c = [self.codeTextView.text characterAtIndex:self.codeTextView.selectedRange.location];
// NSLog(#"cek 2");
}
NSLog(#"c : %d", c);
if (c!=32 && c!=10) {
NSRange delimiter = [self.codeTextView.text rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]
options:NSLiteralSearch
range:NSMakeRange(self.codeTextView.selectedRange.location,
[self.codeTextView.text length] - self.codeTextView.selectedRange.location)];
if (delimiter.location == NSNotFound) {
delimiter.location = [self.codeTextView.text length];
}
self.codeTextView.selectedRange = delimiter;
NSLog(#"delimiter length : %d, location : %d", delimiter.length, delimiter.location);
}
int deviationLocation = self.codeTextView.selectedRange.location - originalPosition.location;
NSString *head = [self.codeTextView.text substringToIndex:self.codeTextView.selectedRange.location];
CGSize initialSize = [head sizeWithFont:self.codeTextView.font constrainedToSize:CGSizeMake(self.codeTextView.frame.size.width,
self.codeTextView.frame.size.height)];
NSUInteger startOfLine = [head length];
BOOL isFirstLine = NO;
NSLog(#"initialize height : %f", initialSize.height);
NSLog(#"code height : %f", self.codeTextView.contentSize.height);
if (initialSize.height / self.codeTextView.contentSize.height == 1) {
isFirstLine = YES;
}
while (startOfLine > 0 && isFirstLine == NO) {
NSRange delimiter = [head rangeOfCharacterFromSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]
options:NSBackwardsSearch range:NSMakeRange(0, startOfLine)];
startOfLine = delimiter.location;
NSString *tempHead = [head substringToIndex:startOfLine];
CGSize tempHeadSize = [tempHead sizeWithFont:self.codeTextView.font constrainedToSize:CGSizeMake(self.codeTextView.frame.size.width,
self.codeTextView.frame.size.width)];
int beforeLine = initialSize.height / self.codeTextView.contentSize.height;
int afterLine = tempHeadSize.height / self.codeTextView.contentSize.height;
if (beforeLine != afterLine)
NSLog(#"break");
break;
}
NSString *tail;
if (isFirstLine == NO) {
tail = [head substringFromIndex:(startOfLine + deviationLocation)];
}else {
tail = [head substringToIndex:startOfLine - deviationLocation];
}
CGSize lineSize = [tail sizeWithFont:self.codeTextView.font forWidth:self.codeTextView.frame.size.width lineBreakMode:UILineBreakModeWordWrap];
cursor = origin;
cursor.x += lineSize.width;
cursor.y += initialSize.height - lineSize.height;
self.codeTextView.selectedRange = originalPosition;
[self.codeTextView becomeFirstResponder];
NSLog(#"cursor x : %f, y : %f", cursor.x, cursor.y);
}
i called that method in textViewShouldBeginEditing method, this is the implementation :
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView{
[self getCursorPosition];
self.viewTextViewScroll.contentSize = CGSizeMake(self.codeTextView.frame.size.width, self.codeTextView.contentSize.height+100);
CGRect frameCode = self.codeTextView.frame;
frameCode.size.height = self.codeTextView.contentSize.height + 103;
self.codeTextView.frame = frameCode;
CGRect frameLine = self.lineNumberTextView.frame;
frameLine.size.height = self.codeTextView.contentSize.height +100;
self.lineNumberTextView.frame = frameLine;
return YES;
}
can somebody help me, please
UPDATE :
i have solved this problem, with using textViewDidChangeSelection delegate method,
here is the code i've made :
-(void)textViewDidChangeSelection:(UITextView *)textView{
counterAppear++;
if (counterAppear == 1) {
}
else if (counterAppear == 2) {
isFistAppear = NO;
codeBuilderSelectedRange = textView.selectedRange;
[self getCursorPosition];
CGFloat xPos = cursor.x - (0.5*self.view.frame.size.width);
if (cursor.x < self.view.frame.size.width) {
[[[self.viewCodeTextView subviews] lastObject] setContentOffset:CGPointMake(0, cursor.y) animated:YES];
}else {
[[[self.viewCodeTextView subviews] lastObject] setContentOffset:CGPointMake(xPos, cursor.y) animated:YES];
}
}else if (isFistAppear == NO) { //kondisi saat keyboard masih appear & user pindah2 kursor
codeBuilderSelectedRange = textView.selectedRange;
[self getCursorPosition];
NSLog(#"cursor x :%f, y : %f", cursor.x, cursor.y);
}
}
i set the BOOL isFirstAppear in viewDidLoad, i set this because, after i nslog the selectedRange.location value in textViewDidChangeSelection, it always called twice, the first value always give a not found value, but the second give a right value, so i set like that. The counterAppear i set in keyboard method (i made a method that called by NSNotification in viewDidLoad). counterAppear is the value that give me the difference condition when i tap the textview.
In textViewDidBeginEditing, the location value will wrong sometimes. To fix this, do this:
-(void)textViewDidBeginEditing:(UITextView *)textView {
[self performSelector:#selector(beginEditing:) withObject:textView afterDelay:0]; }-(void)beginEditing:(UITextView*)sender {
//code here }
Related
I am developing calculater ,
i use sender button tag value to get number from button.. user will not able to enter more than two
digit value in textfield
i.e. 34+22 , 23+22 like this he will able to enter,
234+234 like that he is not able to enter value in textfield.
for that i use
-(IBAction)numberpress:(UIButton *)sender //number select
{
NSString *number = sender.currentTitle;
self.caldisplay.text = [self.caldisplay.text stringByAppendingString:number];
NSLog(#"%# number is caldis",self.caldisplay.text);
}
How i do this , help me thanks..
try
-(IBAction)numberpress:(UIButton *)sender //number select
{
NSString *number = sender.currentTitle;
NSString *calculatingString = nil;
if (self.caldisplay.text.length >= 2) {
NSString *lastTwoChar = [self.caldisplay.text substringFromIndex:[self.caldisplay.text length] - 2];
if ([lastTwoChar integerValue] >= 10) { // last char is content two number
if ([number integerValue] > 0 || [number isEqualToString:#"0"]) {
// invalid case. donothing here
calculatingString = self.caldisplay.text;
} else {
calculatingString = [self.caldisplay.text stringByAppendingString:number];
}
} else {
calculatingString = [self.caldisplay.text stringByAppendingString:number];
}
} else {
calculatingString = [self.caldisplay.text stringByAppendingString:number];
}
// check valid input
if ([self calculate:calculatingString] > 100) {
// invalid case. donothing here
} else {
self.caldisplay.text = calculatingString;
}
NSLog(#"%# number is caldis",self.caldisplay.text);
}
- (NSInteger)calculate:(NSString *)input {
// you need to wite code calculating string here
// example code to calculate only operator '+'
NSArray *operands = [input componentsSeparatedByString:#"+"];
NSInteger result = 0;
for (NSString *operand in operands) {
result = result + [operand integerValue];
}
return result;
}
You can use it like this
- (IBAction)btnNumberClicked:(UIButton*)aButton
{
if([self.caldisplay.text isEqualToString:#"0"])
{
self.caldisplay.text = aButton.currentTitle;
}
else
{
self.caldisplay.text = [self.caldisplay.text stringByAppendingString:aButton.currentTitle];
}
}
OK Here is my Code
It now limits user input to two characters after the decimal and checks for a decimal but now it wont let me add or subtract anymore.... /sigh
//
// CalculatorViewController.h
//
//
#import "ViewController.h"
int Method;
long int SelectNumber;
float RunningTotal;
#interface CalculatorViewController : ViewController <UITextFieldDelegate>
#property (strong, nonatomic) IBOutlet UILabel *screen;
- (IBAction)backToMainViewFromCalculatorViewButton:(id)sender;
#end
and my .m
//
// CalculatorViewController.m
//
//
#import "CalculatorViewController.h"
BOOL isDecimal;
float resultNumber;
float displayNumber;
int operation;
NSUInteger decimalPlacesLimit = 2;
#interface CalculatorViewController ()
#end
#implementation CalculatorViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
// self.screen.text = #"";
isDecimal = false;
resultNumber = 0;
self.screen.adjustsFontSizeToFitWidth = TRUE;
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
/*
#pragma mark - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
// Pass the selected object to the new view controller.
}
*/
- (IBAction)backToMainViewFromCalculatorViewButton:(id)sender {
// Dismiss the VIEW
// DO NOT SEGUE back
// DO THIS
//Going back using segue just stacks views and then soon you run out of memory and APP crashes
[self dismissViewControllerAnimated:YES completion:NULL];
}
-(void)setResultWithNumber:(int)theNumber{
if(!isDecimal){
displayNumber *= 10;
displayNumber += theNumber;
self.screen.text = [NSString stringWithFormat:#"%.0f", displayNumber];
}
else
{
self.screen.text = [self.screen.text stringByAppendingString:[NSString stringWithFormat:#"%d", theNumber]];
}
displayNumber = [self.screen.text floatValue];
}
-(void)operationWithNumber:(int)theNumber{
isDecimal = false;
if(resultNumber == 0){
resultNumber = displayNumber;
}
else{
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
switch (operation) {
case 1:
resultNumber += displayNumber;
break;
case 2:
resultNumber -= displayNumber;
break;
case 3:
resultNumber = displayNumber*resultNumber;
break;
case 4:
resultNumber /= displayNumber;
break;
default:
break;
}
}
operation = theNumber;
displayNumber = 0;
}
- (IBAction)AC:(id)sender {
operation = 0;
resultNumber = 0;
displayNumber = 0;
isDecimal = false;
self.screen.text = [NSString stringWithFormat:#"%i",0];
}
/*
- (IBAction)plus_minus:(id)sender {
displayNumber = 0 - displayNumber;
if(isDecimal)
result.text = [NSString stringWithFormat:#"%.2f", displayNumber];
else
result.text = [NSString stringWithFormat:#"%.0f", displayNumber];
}
- (IBAction)divide:(id)sender {
if(resultNumber != 0){
[self operationWithNumber:operation];
result.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [result.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:4];
}
*/
- (IBAction)seven:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:7];
isDecimal = false;
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
isDecimal = false;
[self setResultWithNumber:7];
}
}
}
- (IBAction)eight:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:8];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:8];
}
}
}
- (IBAction)nine:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:9];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:9];
}
}
}
/*
- (IBAction)multiply:(id)sender {
if(resultNumber != 0){
[self operationWithNumber:operation];
result.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [result.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:3];
}
*/
- (IBAction)six:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:6];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:6];
}
}
}
- (IBAction)five:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:5];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:5];
}
}
}
- (IBAction)four:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:4];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:4];
}
}
}
- (IBAction)substract:(id)sender {
if(resultNumber != 0){
[self operationWithNumber:operation];
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [self.screen.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:2];
}
- (IBAction)three:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:3];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:3];
}
}
}
- (IBAction)two:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:2];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:2];
}
}
}
- (IBAction)one:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
if (range.location == NSNotFound)
{
// No period found
isDecimal = false;
// set the number
[self setResultWithNumber:1];
}
else
{
// If we allready have a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:1];
}
}}
- (IBAction)add:(id)sender {
if(resultNumber != 0)
{
[self operationWithNumber:operation];
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [self.screen.text floatValue];
resultNumber = 0;
}
[self operationWithNumber:1];
}
- (IBAction)zero:(id)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
[self setResultWithNumber:0];
}
else
{
// If we allready ahve a character move on to test for .
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
isDecimal = true;
}
else
{
[self setResultWithNumber:0];
}
}
}
- (IBAction)dot:(id)sender {
isDecimal = true;
NSRange range = [self.screen.text rangeOfString:#"."];
// Search for a Period
// If not found append one to the text
if (range.location == NSNotFound)
{
self.screen.text = [self.screen.text stringByAppendingString:#"."];
}
// We must have a period so now lets test for how many places after the decimal
// and limit it to two
// NSLog(#"text on the way: %#", string);
/*
NSUInteger decimalPlacesLimit = 2;
NSRange rangeDot = [self.screen.text rangeOfString:#"." options:NSCaseInsensitiveSearch];
NSRange rangeComma = [self.screen.text rangeOfString:#"," options:NSCaseInsensitiveSearch];
if (rangeDot.length > 0 || rangeComma.length > 0)
{
if([self.screen.text isEqualToString:#"."])
{
NSLog(#"textField already contains a separator");
}
else
{
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![self.screen.text isEqualToString:#""])
{
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
}
}
}
*/
}
- (IBAction)equals:(id)sender {
[self operationWithNumber:operation];
self.screen.text = [NSString stringWithFormat:#"%.2f",resultNumber];
displayNumber = [self.screen.text floatValue];
resultNumber = 0;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSLog(#"text on the way: %#", string);
NSUInteger decimalPlacesLimit = 2;
NSRange rangeDot = [textField.text rangeOfString:#"." options:NSCaseInsensitiveSearch];
NSRange rangeComma = [textField.text rangeOfString:#"," options:NSCaseInsensitiveSearch];
if (rangeDot.length > 0 || rangeComma.length > 0){
if([string isEqualToString:#"."]) {
NSLog(#"textField already contains a separator");
return NO;
} else {
NSArray *explodedString = [textField.text componentsSeparatedByString:#"."];
NSString *decimalPart = explodedString[1];
if (decimalPart.length >= decimalPlacesLimit && ![string isEqualToString:#""]) {
NSLog(#"textField already contains %lu decimal places", (unsigned long)decimalPlacesLimit);
return NO;
}
}
}
return YES;
}
#end
I cant figure out how to make sure I check for a decimal and then limit entries to no more then two places after the decimal and still be able to calculate
Any help would be appreciated
Travis
I build a calculator application before. What I did was:
Use tag to identify different buttons, 0 is for button 0, 1 is for button 1 ... and 99 is for button dot. All these button triggered the same IBAction function, and in this function just check whether the button user pressed could be added to the Label (You can just use UILabel instead of UITextField here) by applying the rules you created above.
Try this code:
// Assume that, the number buttons have tags from 0 - 9 respectively, and dot button has tag 99
- (IBAction)onButtonPressed:(UIButton *)sender
{
NSRange range = [self.screen.text rangeOfString:#"."];
BOOL canUpdateScreen = YES;
if(range.location != NSNotFound) {
if(sender.tag == 99) {
// Already got dot, cannot show another dot
canUpdateScreen = NO;
} else {
NSArray *explodedString = [self.screen.text componentsSeparatedByString:#"."];
if(explodedString[1].length >= decimalPlacesLimit) {
canUpdateScreen = NO;
}
}
}
if(canUpdateScreen) {
if(sender.tag == 99) {
self.screen.text = [NSString stringWithFormat:#"%#%#", self.screen.text, #"."];
} else {
self.screen.text = [NSString stringWithFormat:#"%#%d", self.screen.text, sender.tag];
}
}
}
Here
-(IBAction)NUmber1:(id)sender{
SelectNumber = SelectNumber * 10;
SelectNumber = SelectNumber + 1;
Screen.text = [NSString stringWithFormat:#"%i", SelectNumber];
}
you are erasing your current string with new INTEGER value. Try to change to
-(IBAction)NUmber1:(id)sender{
Screen.text = [Screen.text stringByAppendingString:#"1"];
}
Also you need to change your SelectNumber calculation logic. I suggest you to get it from string when you really need it
double SelectNumber = [Screen.text doubleValue];
Issue,
The code you have posted is, UITextFieldDelegate method implementation. It will not be triggered, if you update the UITextField text through code.
Solution
You can use any of the below solution.
To simplify the tasks, you can use, tag 0 to 9 for buttons 0 to 9 and some other value for “.” and "," as mentioned in another answer.
// 1. Call the method (in your code) on tapping the buttons (0 - 9 and “.”) and conditionally update the UITextField.text (yourTextField.text), like
update = [self textField:yourTextField shouldChangeCharactersInRange:NSMakeRange(yourTextField.text.length, 1) replacementString:yourButtonLabel];
if (update)
{
// Uodate yourTextField
}
else
{
// Don’t update yourTextField
}
// 2. Before updating yourTextField.text, check there is any decimal point or comma already in yourTextField, using
if([yourTextField.text rangeOfString:#"."].location != NSNotFound) // Update condition to check comma if the tapped button is so
{
// Don’t update yourTextField
}
else
{
// Update yourTextField
}
I was looking into implementing hashtag autocomplete with objective-C as shown in the picture
I found it a bit difficult than expected. I'm looking more specific implementation for adding and deleting hashtags. For example, the hashtag should be deleted as a whole at once. I was wondering if anyone has similar experience implemented it and if there's a more efficient way implemented it. Thanks
I ended up writing some functions that I feel is a bit ugly but it works. Maybe there are some more efficient ways to implement it.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
//user is a singleton instance
User *user = [User sharedUser];
user.autocompleteTableView.hidden = NO;
int identifiedTagsStringLength = [self.identifiedTagsString length];
int cursorLocation = range.location;
//insert characters
if (range.location >= identifiedTagsStringLength) {
NSString *newSearch =#"";
NSRange newRange;
if(identifiedTagsStringLength != 0) {
newSearch = [urlField.text substringFromIndex:identifiedTagsStringLength];
newRange = NSMakeRange(range.location - identifiedTagsStringLength, 0);
}
else {
newSearch = textField.text;
newRange = range;
}
NSString *substring = [NSString stringWithString:newSearch];
substring = [substring stringByReplacingCharactersInRange:newRange withString:string];
[self searchAutocompleteEntriesWithSubstring:substring];
if (cursorLocation > currentTagsRange) {
currentTagsRange = cursorLocation;
}
}
//delete tags
else {
if ([self.ranges count] != 0 && cursorLocation < currentTagsRange) {
int rangeLength = [self.ranges count];
int toBeRemovedIndex = 0;
for (int i = 0; i< rangeLength; i++) {
if (cursorLocation >= [[self.ranges objectAtIndex:i][0] intValue]
&& cursorLocation <= [[self.ranges objectAtIndex:i][1] intValue]) {
toBeRemovedIndex = i;
}
}
[self.tags removeObjectAtIndex:toBeRemovedIndex];
[self updateRanges];
NSString *outputString = #"";
for (NSString *tag in self.tags) {
outputString = [NSString stringWithFormat:#"%##%# ", outputString,
tag];
}
urlField.text = outputString;
self.identifiedTagsString = urlField.text;
currentTagsRange = [outputString length] - 1;
}
}
return YES;
}
- (void)updateRanges {
self.ranges = [[NSMutableArray alloc] init];
int startIndex = 0;
for (NSString *tag in self.tags) {
startIndex = [self.ranges count] == 0 ? 0 : [[self.ranges lastObject][1] intValue] + 1;
int tagLength = [tag length];
NSArray *range = [NSArray arrayWithObjects:[NSNumber numberWithInt:startIndex], [NSNumber numberWithInt:startIndex + tagLength + 1], nil];
[self.ranges addObject: range];
}
}
#pragma mark UITableViewDataSource methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *selectedCell = [tableView cellForRowAtIndexPath:indexPath];
if (self.identifiedTagsString == NULL) {
self.identifiedTagsString = #"";
}
[self.tags addObject: selectedCell.textLabel.text];
[self updateRanges];
NSString *output = #"";
for (NSString *tag in self.tags) {
output = [NSString stringWithFormat:#"%##%# ", output, tag];
}
urlField.text = output;
User *user = [User sharedUser];
user.autocompleteTableView.hidden = YES;
self.identifiedTagsString = urlField.text;
currentTagsRange = [urlField.text length];
}
To get the part of the NSString which is displayed in the view.
Or
To get the NSString which can be displayed in the given CGRect.
It returns the string which is displayed on the view (UIlabel, UITextFiled etc).
This is useful when the string to large and view is not long enough to display the whole string.
So i have written the code and added it here.
//If you want the string displayed in any given rect, use the following code..
#implementation NSString (displayedString)
//font- font of the text to be displayed
//size - Size in which we are displaying the text
-(NSString *) displayedString:(CGSize)size font:(UIFont *)font
{
NSString *written = #"";
int i = 0;
int currentWidth = 0;
NSString *nextSetOfString = #"";
while (1)
{
NSRange range;
range.location = i;
range.length = 1;
NSString *nextChar = [self substringWithRange:range];
nextSetOfString = [nextSetOfString stringByAppendingString:nextChar];
CGSize requiredSize = [nextSetOfString sizeWithFont:font constrainedToSize:CGSizeMake(NSIntegerMax, NSIntegerMax)];
currentWidth = requiredSize.width;
if(size.width >= currentWidth && size.height >= requiredSize.height)
{
written = [written stringByAppendingString:nextChar];
}
else
{
break;
}
i++;
}
return written;
}
#end
I have developed an application which displays PDF on ipad using CATiled Layer. So far, so good& But there is a problem which really makes me cut my last hairs. I have a PDF with embedded annotations. Each annotation has an URL. I can find the coordinates of touch area but the question is how I can find if there is an annotation under my finger and how to extract URL to open it in browser?
If anyone can share any thoughts about how this can be done, I will really appreciate your help!
Thanks in advance
Getting the annotations isn't very difficult with CGPDF although it required a little fiddling around for me at first.
//Get the current page ref
CGPDFPageRef currentPdfPage = CGPDFDocumentGetPage(pdfDocumentRef, page);
//Get the page dictionary
CGPDFDictionaryRef pageDictionary = CGPDFPageGetDictionary(currentPdfPage);
CGPDFArrayRef annotsArray;
//Get the Annots array
if(!CGPDFDictionaryGetArray(pageDictionary, "Annots", &annotsArray)) {
//DLog(#"No Annots found for page %d", page);
[self updateProgress];
return;
}
int annotsArrayCount = CGPDFArrayGetCount(annotsArray);
//DLog(#"%d annots found for page %d in file %#", annotsArrayCount, page, _fileName);
NSMutableArray* touchRectsArray = [[NSMutableArray alloc] initWithCapacity:annotsArrayCount];
for (int j=annotsArrayCount; j >= 0; j--) {
int destPageNumber = 0;
NSString* uri = nil;
//DLog(#"%d/%d", j+1, annotsArrayCount);
CGPDFObjectRef aDictObj;
if(!CGPDFArrayGetObject(annotsArray, j, &aDictObj)) {
//DLog(#"%#", #"can't get dictionary object");
continue;
}
CGPDFDictionaryRef annotDict;
if(!CGPDFObjectGetValue(aDictObj, kCGPDFObjectTypeDictionary, &annotDict)) {
//DLog(#"%#", #"can't get annotDict");
continue;
}
//------------
CGPDFDictionaryRef aDict;
CGPDFArrayRef destArray;
if(CGPDFDictionaryGetDictionary(annotDict, "A", &aDict)) {
CGPDFStringRef uriStringRef;
if(CGPDFDictionaryGetString(aDict, "URI", &uriStringRef)) {
char* uriString = (char *)CGPDFStringGetBytePtr(uriStringRef);
uri = [NSString stringWithCString:uriString encoding:NSUTF8StringEncoding];
}
} else {
continue;
}
This will get you the URLs.
Getting the rects:
CGPDFArrayRef rectArray;
if(!CGPDFDictionaryGetArray(annotDict, "Rect", &rectArray)) {
DLog(#"%#", #"can't get Rect");
}
int arrayCount = CGPDFArrayGetCount(rectArray);
CGPDFReal coords[4];
for (int k = 0; k < arrayCount; k++) {
CGPDFObjectRef rectObj;
if(!CGPDFArrayGetObject(rectArray, k, &rectObj)) {
DLog(#"%#", #"can't get rect data");
}
CGPDFReal coord;
if(!CGPDFObjectGetValue(rectObj, kCGPDFObjectTypeReal, &coord)) {
DLog(#"%#", #"can't get coords");
}
coords[k] = coord;
}
CGRect drawRect = [[SharedConfig valueForKey:#"screenSize"] CGRectValue];
BOOL drawBoxIsLandscape = NO;
if (1 < drawRect.size.width/drawRect.size.height) {
drawBoxIsLandscape = YES;
}
CGRect pageRect = CGRectIntegral(CGPDFPageGetBoxRect(currentPdfPage, kCGPDFMediaBox));
landscape = NO;
if (1 < pageRect.size.width/pageRect.size.height) {
landscape = YES;
}
float ratio = 0.0;
//Get the rect of the clickable area
//CGRect coordsRect = CGRectMake(coords[0], coords[1], coords[2], coords[3]);
//Transform to new coordinate system
CGRect originalRect = CGRectMake(coords[0], (pageRect.size.height-(coords[3]-coords[1]))-coords[1], coords[2]-coords[0], coords[3]-coords[1]);
CGPDFInteger pageRotate = 0;
CGPDFDictionaryGetInteger(pageDictionary, "Rotate", &pageRotate);
if (pageRotate == 90 || pageRotate == 270) {
CGFloat temp = pageRect.size.width;
pageRect.size.width = pageRect.size.height;
pageRect.size.height = temp;
ratio = drawRect.size.height / pageRect.size.height;
}
if (drawBoxIsLandscape) {
ratio = landscape ? (drawRect.size.height/pageRect.size.height) : (drawRect.size.height/pageRect.size.width);
if (landscape && drawRect.size.width < pageRect.size.width*ratio) {
ratio = drawRect.size.width/pageRect.size.width;
} else if (!landscape && drawRect.size.height < pageRect.size.width*ratio) {
ratio = drawRect.size.height/pageRect.size.width;
}
} else {
ratio = landscape ? (drawRect.size.height/pageRect.size.width) : (drawRect.size.height/pageRect.size.height);
if (landscape && drawRect.size.width < pageRect.size.height*ratio) {
ratio = drawRect.size.width/pageRect.size.height;
} else if (!landscape && drawRect.size.height < pageRect.size.height*ratio) {
ratio = drawRect.size.height/pageRect.size.height;
}
}
CGRect calculatedRect = CGRectMake(originalRect.origin.x*ratio, originalRect.origin.y*ratio, originalRect.size.width*ratio, originalRect.size.height*ratio);
if ((landscape && !drawBoxIsLandscape) || (!landscape && drawBoxIsLandscape)) {
CGFloat width = calculatedRect.size.width;
calculatedRect.size.width = calculatedRect.size.height;
calculatedRect.size.height = width;
CGFloat yModifier = drawRect.size.height-(pageRect.size.width*ratio);
CGFloat x = calculatedRect.origin.x;
calculatedRect.origin.x = calculatedRect.origin.y;
calculatedRect.origin.y = drawRect.size.height-(x+calculatedRect.size.height)-yModifier;
}
if (nil != uri) {
[touchRectsArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:[NSArray arrayWithCGRect:calculatedRect], #"rect", uri, #"targetUrl", nil]];
}
As you can see this bit of code first gets the rectangle of the annotation, transforms it to the device coordinate system, then does some recalculation, sizing and repositioning, based on page to screen ratio, rotation factor, etc. At the end you will have an array of the touch-active areas for that page. For handling the touches the following simple solution can be used:
- (void) tapGesture:(UIGestureRecognizer*)sender
{
if (UIGestureRecognizerStateEnded == sender.state) {
CGPoint touchPoint = [sender locationInView:self.view];
if (nil != self.touchRects) for (int i=0; i<[self.touchRects count]; i++) {
if (CGRectContainsPoint([[[self.touchRects objectAtIndex:i] objectForKey:#"rect"] CGRectValue], touchPoint)) {
if ([[self.touchRects objectAtIndex:i] objectForKey:#"targetUrl"]) {
NSString* targetUrl = [[self.touchRects objectAtIndex:i] objectForKey:#"targetUrl"];
DLog(#"Hit found for target url: %#", targetUrl);
NSURL* url = [NSURL URLWithString:targetUrl];
[[UIApplication sharedApplication] openURL:url];
} return;
}
}
DLog(#"No hit found for touch at %#", NSStringFromCGPoint(touchPoint));
}
}