I have a UITextView in my app which is used to display some string with numbers. These numbers can be phone numbers or other numbers specific to the app. If user taps the phone number they should be asked if they want to make a call (This is done by default). However, if the user taps on the other number which is not a phone number but is specific to the app, the action should be custom e.g. it should call a method in the view controller with the number as an argument.
I had a quick search but couldn't find any easy solution.
Any idea how this could be done? Any help would be appreciated.
I was able to solve this by implementing 'UITextViewDelegate'.
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
DLog(#"URL is %#", URL.absoluteString)
NSString *host = [URL host];
NSNumber *adIdInLink = nil;
NSRange range = NSMakeRange(0, 3);
NSString *subString = [URL.absoluteString substringWithRange:range];
if ([subString isEqualToString:#"tel"] && !host) {
// this is a number - do whatever you want
NSString *stringWithAdId = [URL.absoluteString substringFromIndex:4];
if ([HJUtilities isValidAdId:stringWithAdId]) {
// this is the ad Id a custom number used in my app
adIdInLink = [NSNumber numberWithInteger:[stringWithAdId integerValue]];
}
else {
// this was a phone number - let the default behaviour
return YES;
}
}
}
Add a UITapGestureRecognizer to the UITextView with its delegate set to the parent UIViewController:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(handleTap:)];
[myTextView addGestureRecognizer:recognizer];
The add a handler method to your UIViewController:
- (void)handleTap:(UITapGestureRecognizer *)sender{
UITextView *textView = (UITextView*)sender;
NSString *number = textView.text;
}
eta: You need to add UIGestureRecognizerDelegate as a protocol to your UIViewController too:
#interface MyViewController : UIViewController <UIGestureRecognizerDelegate>
update:
How about using the UITextViewDelegate method textViewDidChangeSelection instead?
Inspect the selectedRange property of the textview to check its contents.
Related
I'm using a swift framework and I need to be able to have a # like tag inside of a message text string. If a user sends a message containing a code prefixed with a #, for example: #t3Us9K that single "word" needs to be a link that all users can press. please tell me if this is possible and if so how to do it.
I had been working on your question and this are my results,
First of all you need to modify JSQMessagesCellTextView in the library and add a method to help you to detect custom links, like #test, If you want you can clone my fork with this feature added and this is how it looks, the animation issue is because my gif converter
EDITED
https://github.com/rmelian2014/JSQMessagesViewController/tree/contributing
- (void)detectCustomLinks
{
NSMutableArray<NSTextCheckingResult*> *textCheckingResults = [NSMutableArray<NSTextCheckingResult*> array];
for (NSRegularExpression *exp in [self.regularExpressionsDelegate getRegularExpressions]) {
NSArray<NSTextCheckingResult*> * matches = [exp matchesInString:self.text options:NSMatchingWithoutAnchoringBounds range:NSMakeRange(0, self.text.length)];
[textCheckingResults addObjectsFromArray:matches];
}
NSMutableAttributedString * str2 = [[NSMutableAttributedString alloc] initWithAttributedString:self.attributedText];
[str2 removeAttribute:NSLinkAttributeName range:NSMakeRange(0, str2.string.length)];
for (NSTextCheckingResult * match in textCheckingResults) {
[str2 addAttribute: NSLinkAttributeName value:[str2 attributedSubstringFromRange:match.range].string range:match.range];
}
self.attributedText = str2;
}
I had added a delegate to provide regular expressions to check with
#protocol RegularExpressionDelegate <NSObject>
-(NSArray<NSRegularExpression*>*)getRegularExpressions;
#end
/**
* `JSQMessagesCellTextView` is a subclass of `UITextView` that is used to display text
* in a `JSQMessagesCollectionViewCell`.
*/
#interface JSQMessagesCellTextView : UITextView
#property (weak) id<RegularExpressionDelegate> regularExpressionsDelegate;
#end
and then you need to put your viewController as UITextViewDelegate and finally in your cellForRowAtIndexPath you need to put something like this
cell.textView.regularExpressionsDelegate = self;
cell.textView.delegate = self;
this will put your viewController as regularExpressionsDelegate and then you need to implement this method, returning the regular expressions that you want be detected as customs links
- (NSArray<NSRegularExpression*>*)getRegularExpressions
{
return [NSArray arrayWithObject:[NSRegularExpression regularExpressionWithPattern:#"#([a-zA-Z0-9])+" options:0 error:NULL]];
}
also you need to implement this
//UITextViewDelegateMethod
- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)URL inRange:(NSRange)characterRange
{
//URL param will provide the link that was touched ej:#test
return YES;
}
I Hope this helps you, Regards
I want to set a range i.e between $350000 & $800000 for a text field. If the entered number is less than this range then an alert message should pop up. Plz help
In Objective-C using NSNumberFormatter you can use like this:
NSNumberFormatter *nf = [[NSNumberFormatter alloc] init];
nf.minimum = #10;
nf.maximum = #20;
NSNumber *a = [nf numberFromString:#"12"];
NSNumber *b = [nf numberFromString:#"22"];
//if the number is beyond the range, it will return nil, so in above b is nil
//so the following check will show the log and alert
if (!a || !b) {
NSLog(#"Either of them is out of range");
//show the UIAlert here
}
EDIT:
You should call the above in following scenarios:
User enter a value and does some action. In the beginning of the action use the above.
Or every time the user enters and moves away from the textfield, then call the above in textFieldDidEndEditing: delegate method of UITextField.
Note: Since your number has $ prefixed, please make sure you set the currency style in nf as well, instead of trimming it.
Use - (void) textViewDidEndEditing:(UITextView *)textView method to check the intergerValue of text and if it doesn't match your requirement show the popup.
If wanna check it on the go use - (BOOL) textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
NSString *finalText = [textView.text stringByAppendingPathComponent:text];
finalText = [finalText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
//enable button
if ([finalText integerValue] < 350000 || [finalText integerValue] > 850000) //do what you wanna do
Make sure $ is out of textfield or you remove it before using integerValue.
This can be helpfull
-(void)viewDidLoad
{
[txt addTarget:self action:#selector(CheckingMethod:) forControlEvents:UIControlEventValueChanged];
}
-(IBAction)CheckingMethod:(id)sender
{
if(txt.text.length>9) {
//your popupview
}
else{
}
}
I'm trying to create something in UITextView that every time the text encounter this kind of symbol "#". All text after that symbol will send to other controller.
here's my code
- (BOOL)textView:(UITextView *)aTextView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// "Length of existing text" - "Length of replaced text" + "Length of replacement text"
NSInteger newTextLength = [self.addingText.text length] - range.length + [text length];
if([text isEqualToString:#"#"] || secondString){
secondString = true;
NSString * stringToRange = [self.addingText.text substringWithRange:NSMakeRange(0,range.location)];
// Appending the currently typed charactor
stringToRange = [stringToRange stringByAppendingString:text];
// Processing the last typed word
NSArray *wordArray = [stringToRange componentsSeparatedByString:#"#"];
self.getSecondString = [wordArray lastObject];
// wordTyped will give you the last typed object
NSLog(#"\nWordTyped : %#",self.getSecondString);
}
if (newTextLength > 50) {
// don't allow change
[aTextView resignFirstResponder];
return NO;
}
self.countChar.text = [NSString stringWithFormat:#"%li", (long)newTextLength];
return YES;
}
I get that code from here. It is perfectly work when I use NSLog but the time I click the button to send it to other controller using segue. It always show Null value. Hoping your help here. Thanks in advance
here's my button code
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
CameraViewController * cameraViewController = (CameraViewController *)[segue destinationViewController];
if([segue.identifier isEqualToString:#"createText"]){
NSLog(#"prepareForSegue: %# == %#", self.addingText.text,self.getSecondString);
cameraViewController.inputCreateText = [NSString stringWithFormat:#"%#", self.addingText.text];
cameraViewController.secondInputCreateText =[NSString stringWithFormat:#"%#", self.getSecondString];
}
}
I think your problem is you are modifying your string inside textView:shouldChangeTextInRange: which gets called for every typed in character. If your purpose is to send the string after # on tap on a button then do the calculation on button handler or inside prepareForSegue:sender:. If for some reason you want to stick to your own implementation then I would advise to put your targeted string inside NSMutableArray property created at class level so you don't loose data. And then you can combine all string inside that array like this [arrayOfStrings componentsJoinedByString:#" "].
Thanks in advance for your help. I try to boost my learning curve of Objective-c and defy myself with a lot of cases.
I try to do a simple app which simulates the comportment of a terminal session:
First step: a prompt is waiting and I enter a first command: eg. date. Then I get a result. Second: a prompt is waiting again below the result. Then I give a second command: time
etc.
I did a lot of tests with an UItextField to input different texts and commands, and a UITextView to display the results. I also use an NSMutable Array to stock all inputs/results. Nothing work very well. I would like to get your advice on that matter and that you point me the best approach or a code source to learn to reproduce a terminal gui. Is an array a good solution, how to place the textField at the end of the textView, etc.? Thanks+
This is just a general approach of what you want to achieve.
Use a single UITextView for input and output.
At first, add a simple character to your UITextView, for example ">", so the user starts typing after this character.
Implement this UITextView delegate method to listen on when the user taps "return" :
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if([text isEqualToString:#"\n"]) {
// Handle what happens when user presses return
return NO;
}
return YES;
}
What I would do here is when the user presses return, get the whole UITextField's content, and use something like NSArray *stringArray = [_textField.text componentsSeparatedByString: #">"];. That way, the last element of your array is the last command the user entered. Then, test the command and append the appropriate answer to the UITextView. Don't forget to add #"\n>" after it so you prompt the user a new command line.
What's left to do here is prevent the user from erasing your ">".
It's an idea, there's probably many other ways to do it. Comment if you need more details on something !
SPOILER ALERT : full code
In my storyboard, I simply have a UITextView linked to ViewController.h, with the name textView. Note that the following code does not handle the user removing text from the UITextView. You can test the code by typing "hello" in the console.
ViewController.m :
#import "ViewController.h"
#interface ViewController () {
// Store supported commands and outputs
NSDictionary *commands;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Initialize textView
_textView.text = #">";
[_textView becomeFirstResponder];
// Init supported commands with associated output
commands = [NSDictionary dictionaryWithObjectsAndKeys:#"Hello World !", #"hello", nil];
}
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
// Deleting something
if([text isEqualToString:#""]) {
UITextPosition *beginning = textView.beginningOfDocument;
UITextPosition *start = [textView positionFromPosition:beginning offset:range.location];
UITextPosition *end = [textView positionFromPosition:start offset:range.length];
UITextRange *textRange = [textView textRangeFromPosition:start toPosition:end];
NSString *textToReplace = [textView textInRange:textRange];
NSLog(#"%#", textToReplace);
if ([textToReplace isEqualToString:#">"]) return NO;
return YES;
}
if([text isEqualToString:#"\n"]) {
// Handle what happens when user presses return
NSArray *stringArray = [_textView.text componentsSeparatedByString: #">"];
NSLog(#"Last command : %#", [stringArray lastObject]);
[self handleCommand:[stringArray lastObject]];
return NO;
}
return YES;
}
-(void)handleCommand:(NSString*)command {
NSString *output = [commands objectForKey:command];
// If an unsupported command was typed
if (output == nil) {
output = #"Unknown command";
}
// Write output to the textView
_textView.text = [_textView.text stringByAppendingString:#"\n"];
_textView.text = [_textView.text stringByAppendingString:output];
_textView.text = [_textView.text stringByAppendingString:#"\n>"];
}
This is the textView's content from the simulator :
>hello
Hello World !
>yes
Unknown command
>
Unknown command
>hello
Hello World !
Many ways to do this. If I were doing it I think I would do the following:
A dictionary to hold the input and output for each command
Very large UITextView with all entries in the dictionary outputted in the format you like
A no border UITextField to act as the prompt.
You would have to write the following:
A method to place the UITextFiled at the right line of the UITextField
A method to populate the Diciotnary
A method to populate the UITextField from the dictionary
What I have :
TextView
NSArray (string)
AVAudioplayer (not yet implemented)
When I select a word in TextView :
• Check if word exist in Array
• Start Audioplayer with associated sound
Unfortunately when I tap twice to select a word inside TextView, textViewDidChangeSelection is called twice. I don’t know why I see "Youpie" twice.
I just changed inputView to hide keyboard because I only need TextView to be used in selecting mode.
- (void)textViewDidChangeSelection:(UITextView *)tve;
{
NSString *selectedText = [tve textInRange:tve.selectedTextRange];
if(selectedText.length > 0)
{
for (NSString *text in textArray)
{
if ([selectedText isEqualToString:text])
NSLog(#"Youpie");
tve.selectedTextRange = nil;
if (ps1.playing == YES)
{
[self stopEveryPlayer];
[self updateViewForPlayerState:ps1];
}
else if ([ps1 play])
{
[self updateViewForPlayerState:ps1];
fileName.text = [NSString stringWithFormat: #"%# (%d ch.)", [[ps1.url relativePath] lastPathComponent], ps1.numberOfChannels, nil];
}
else
NSLog(#"Could not play %#\n", ps1.url);
break;
}
}
}
}
- (void)awakeFromNib
{
textArray = [[NSArray alloc] initWithObjects:#"dog",#"cat",#"person",#"bird",#"mouse", nil];
textView.inputView = [[[UIView alloc] initWithFrame:CGRectZero] autorelease];
textView.delegate = self;
// ...
}
I noticed something when I was double tapping on each good word in my text.
textViewDidChangeSelection
If a word is already selected and no action choosen, I have 1 "Youpie".
If not, I have 2 "Youpie".
I found a simple solution. I removed selectedRange after getting value. textViewDidChangeSelection called once.
What I have changed
tve.selectedTextRange = nil;
I use a subclass of UITextView to disable menu.
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender
{
return NO;
return [super canPerformAction:action withSender:sender];
}
I added an implementation for AVAudioPlayer (ps1) too.
My "autoplay" is working if a known word is selecting :)
I don't have an answer for why the method gets called twice or how to prevent this, but an alternative solution might be to display an additional item in the edit menu that pops up in a text view when a word is double clicked. Then, your action for initiating a sound based on the word could be triggered from the action selector defined in that additional menu item. In this design, you'd remove your textViewDidChangeSelection and thus would not get called twice. See http://developer.apple.com/library/ios/#documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/AddingCustomEditMenuItems/AddingCustomEditMenuItems.html for some additional info about modifying the standard menu.