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
Related
I have a UITableView with custom UITableViewCells and each tableview cell has a UITextField. By default the textfield has a title already in place that can be edited by the user. The default title in the textfield is associated with a file in NSFileManager and when the user finishes editing the text field and taps "return", a method that changes the file name to what the user enters gets called. This works fine, but when the user taps the textfield but doesn't do any editing and then taps "back" to go to the previous view controller, I get a warning from NSFileManager saying the file name already exists. This doesn't cause any problems, but its annoying. I know the method that calls NSFileManager to change the file name shouldn't get called unless the user edits the textfield, but I'm not sure of the best way to implement this.
I saw this post, but wasn't sure how to incorporate it into what I'm doing:
UITextField text change event
I was wondering if someone could give me some tips on how to make this work.
-(void) textFieldDidEndEditing:(UITextField *)textField
{
textField.delegate = self;
NSArray* cells = [self.audioTable visibleCells];
for (OSAudioTableCell* cell in cells)
{
if (textField == cell.textField)
{
NSInteger index = cell.tag;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:0];
Recording * recording = [self.fetchCon objectAtIndexPath:indexPath];
NSString * previousPath = recording.audioURL;
//I left a lot out, but this is where I call the method to change the file name
NSString * returnedURL = [self.managedDocument changeFileName:previousPath withNewComponent:textField.text error:&aError];
}
}
}
I would just check if the textField's text changed. If it did then go through the block you pasted above. If not, then just do nothing. You can do this by holding a temporary reference to your textfield's value before any edits happen:
// At the top of your class
#property (strong, nonatomic) NSString *currentFileName;
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
_currentFileName = textField.text;
}
Then in your method above, I would check if the two strings are not equal:
-(void) textFieldDidEndEditing:(UITextField *)textField
{
if (![textField.text isEqualToString:currentFileName]) {
// continue with your logic
}
}
Try this. Add the delegate method -(void)textFieldDidBeginEditing:(UITextField *)textField. And in this method do something like:
-(void)textFieldDidBeginEditing:(UITextField *)textField {
self.textBeforeEditing = textField.text;
}
And then, do a compare when textFieldDidEndEditing is invoked:
-(void) textFieldDidEndEditing:(UITextField *)textField {
...
if(![self.textBeforeEditing isEqualToString:textField.text]) {
// Change the file name
}
...
}
You could implement textFieldDidBeginEditing:, in which you would store the unedited value of the UITextField in an instance variable. Then, in textFieldDidEndEditing: simple compare the before and after values, and if they're different call your NSFileManager method like you normally would.
Example
#interface MyClass () {
#property (strong, nonatomic) NSString *originalText;
}
#implementation MyClass
- (void)textFieldDidBeginEditing:(UITextField *)textField {
self.originalText = textField.text;
}
- (void)textFieldDidEndEditing:(UITextField *)textField {
if ([self.originalText isEqualToString:textField.text]) {
// Your original code here.
}
self.originalText = nil;
}
#end
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
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.
Right now, our code is set to grab the text from the UITextField as setInitialText for Facebook/Twitter posts.
What we want to do is: add an additional permanent message or URL to the Facebook/Twitter posts.
How can we do this? Here's our current code:
[slComposeThirdViewController setInitialText:[[self QText]text]];
[self presentViewController:slComposeThirdViewController animated:YES completion:nil];
It's a little involved, so bear with me here... and this only works for SLServiceTypeTwitter
For anyone reading this that is interested in using this, I've put a sample project on Github: https://github.com/NSPostWhenIdle/Immutable-SLComposeViewController
The first thing you'll want to do is make sure that your view controller conforms to UITextViewDelegate. You'll also want to create an iVar for a UITextView. You won't actually be creating a text view, but you'll want to have a pointer to assign directly to the text view inside the SLComposeViewController. While you're here make a iVar for the permanent string as well.
#interface ViewController : UIViewController <UITextViewDelegate> //very important!
{
UITextView *sharingTextView;
NSString *permanentText;
}
Then in viewDidLoad you can set up what you want the permanent text to be:
- (void)viewDidLoad
{
[super viewDidLoad];
permanentText = #"http://www.stackoverflow.com/";
}
The code below is a pretty basic IBAction to present the composer with a couple of slight tweaks. First, you'll notice that setInitialText uses a formatted string the append the permanent text to the end of the contents of the text field with a space added in between.
Then comes the important part! I've added a loop to presentViewController:'s completion handler to cycle through some subviews of subviews of subviews in order to identify the UITextView in the composer that contains the sharing text. This needs to be done so you can set that text view's delegate in order to access the UITextViewDelegate method shouldChangeTextInRange.
- (IBAction)exampleUsingFacebook:(UIButton *)sender {
if([SLComposeViewController isAvailableForServiceType:SLServiceTypeTwitter])
{
SLComposeViewController *sharingComposer = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
SLComposeViewControllerCompletionHandler __block completionHandler=^(SLComposeViewControllerResult result){
[sharingComposer dismissViewControllerAnimated:YES completion:nil];
};
[sharingComposer setCompletionHandler:completionHandler];
[sharingComposer setInitialText:[NSString stringWithFormat:#"%# %#",[[self QText]text],permanentText]];
[self presentViewController:sharingComposer animated:YES completion:^{
for (UIView *viewLayer1 in sharingComposer.view.subviews) {
for (UIView *viewLayer2 in viewLayer1.subviews) {
if ([viewLayer2 isKindOfClass:[UIView class]]) {
for (UIView *viewLayer3 in viewLayer2.subviews) {
if ([viewLayer3 isKindOfClass:[UITextView class]]) {
[(UITextView *)viewLayer3 setDelegate:self];
sharingTextView = (UITextView *)viewLayer3;
}
}
}
}
}
}];
}
}
Important: Please note that the above will only work if placed in the completion handler.
Below is an example of how to set up shouldChangeTextInRange to compare the range that the user is attempting to edit to the range that contains your permanent text. By doing so, the user will be able to make changes to any part of the text that they want... except for the part that contains your permanent text. You'll also notice that inside this method I've compared textView to shareingTextView, the pointer we assigned to the text view inside the composer. Doing so will allow you to use other text views within this controller without them following the same rules I've configured for the text view inside the composer.
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text
{
if (textView == sharingTextView) {
NSRange substringRange = [textView.text rangeOfString:permanentText];
if (range.location >= substringRange.location && range.location <= substringRange.location + substringRange.length) {
return NO;
}
}
return YES;
}
Hope this helps!
You can roll your own composer, append your text and then use SLRequest to actually submit it to the service.
How about capturing the user entered text from the UITextField and then constructing a final string which appends the permanent message you want from it?
UITextField *textField;
NSString *enteredText = [textField text];
NSString *finalPost = [NSString stringWithFormat:#"%# Your permanent text/URL", enteredText];
Now post the "finalPost" string to Facebook or Twitter.
If you want to append the text with URL, just use the addURL: method. It won't display any text in the SLComposeViewController's view. But will add the URL at the end after publishing users tweet.
SLComposeViewController *slComposeThirdViewController = [SLComposeViewController composeViewControllerForServiceType:SLServiceTypeTwitter];
[slComposeThirdViewController setInitialText:#"initial Text"];
[slComposeThirdViewController addURL:[NSURL URLWithString:#"http://stackoverflow.com/"]];
I am working on Text to speech application in iPhone,
in which have a text field that takes input, i want user to select some portion of text from text field and my application will convert that selected text into speech.
my problem is how would i get the text that user has selected from text field?
-[UITextField selectedText]
Although UITextField doesn't have a selectedText method, it conforms to the UITextInput protocol. So, you can use the required properties & methods of the UITextInput protocol to determine the selectedText of a UITextField *textField (or any object that conforms to the UITextInput protocol, such as a UITextView).
NSString *selectedText = [textField textInRange:textField.selectedTextRange];
NSLog(#"selectedText: %#", selectedText);
As an aside, you can also use the required properties & methods of the UITextInput to calculate the selectedRange of a UITextField *textField.
UITextRange *selectedTextRange = textField.selectedTextRange;
NSUInteger location = [textField offsetFromPosition:textField.beginningOfDocument
toPosition:selectedTextRange.start];
NSUInteger length = [textField offsetFromPosition:selectedTextRange.start
toPosition:selectedTextRange.end];
NSRange selectedRange = NSMakeRange(location, length);
NSLog(#"selectedRange: %#", NSStringFromRange(selectedRange));
-[UITextFieldDelegate textFieldDidChangeSelection:]
Although, UITextFieldDelegate doesn't declare a textFieldDidChangeSelection: delegate method like -[UITextViewDelegate textViewDidChangeSelection:], you can still hook into when the selection of a UITextField has changed. To do so, subclass UITextField and use method swizzling to add your own code to the textField.inputDelegate's native implementation of -[UITextInputDelegate selectionDidChange:].
// MyTextField.h
#import <UIKit/UIKit.h>
#interface MyTextField : UITextField
#end
// MyTextField.m
#import <objc/runtime.h>
#import "MyTextField.h"
UIKIT_STATIC_INLINE void mySelectionDidChange(id self, SEL _cmd, id<UITextInput> textInput);
#implementation MyTextField {
BOOL swizzled;
}
#pragma mark - UIResponder
// Swizzle here because self.inputDelegate is set after becomeFirstResponder gets called.
- (BOOL)becomeFirstResponder {
if ([super becomeFirstResponder]) {
[self swizzleSelectionDidChange:YES];
return YES;
} else {
return NO;
}
}
// Unswizzle here because self.inputDelegate may become the inputDelegate for another UITextField.
- (BOOL)resignFirstResponder {
if ([super resignFirstResponder]) {
[self swizzleSelectionDidChange:NO];
return YES;
} else {
return NO;
}
}
#pragma mark - Swizzle -[UITextInput selectionDidChange:]
// Swizzle selectionDidChange: to "do whatever you want" when the text field's selection has changed.
// Only call this method on the main (UI) thread because it may not be thread safe.
- (void)swizzleSelectionDidChange:(BOOL)swizzle {
if (swizzle == swizzled || ![self respondsToSelector:#selector(inputDelegate)]) return; // 4.3
Class inputDelegateClass = object_getClass(self.inputDelegate);
SEL mySelector = #selector(mySelectionDidChange:);
class_addMethod(inputDelegateClass, mySelector, (IMP)mySelectionDidChange, "v#:#");
Method myMethod = class_getInstanceMethod(inputDelegateClass, mySelector);
Method uiKitMethod = class_getInstanceMethod(inputDelegateClass, #selector(selectionDidChange:));
method_exchangeImplementations(uiKitMethod, myMethod);
swizzled = swizzle;
// NSLog(#"swizzled? %i", method_getImplementation(uiKitMethod) == (IMP)venmo_selectionDidChange);
}
#end
UIKIT_STATIC_INLINE void mySelectionDidChange(id self, SEL _cmd, id<UITextInput> textInput) {
// Call the native implementation of selectionDidChange:.
[self performSelector:#selector(mySelectionDidChange:) withObject:textInput];
// "Do whatever you want" with the selectedText below.
NSString *selectedText = [textInput textInRange:textInput.selectedTextRange];
NSLog(#"selectedText: %#", selectedText);
}
I did solve my query as follow :
I implement UITextView's delegate and implement following method
- (void)textViewDidChangeSelection:(UITextView *)textView {
NSRange r = textView.selectedRange;
NSLog(#"Start from : %d",r.location); //starting selection in text selection
NSLog(#"To : %d",r.length); // end position in text selection
NSLog([tv.text substringWithRange:NSMakeRange(r.location, r.length)]); //tv is my text view
}
That's it!
Swift
In Swift, getting the selected text from a UITextField is done like this:
if let textRange = myTextField.selectedTextRange {
let selectedText = myTextField.textInRange(textRange)
}
where textRange is a UITextRange that is used to get the actual selected text.
A similar topic is discussed here: Can I select a specific block of text in a UITextField?
AFAIK there is no event if text is selected. However, you could setup an NSTimer to watch your textfield and check the _selectedRange. If it changes, go fire up your text-to-speech code.
EDIT: I was wrong about the selection. UITextField cannot do what you want to achieve. But if you use UITextView instead, you can implement its UITextViewDelegate and override
- (void)textViewDidChangeSelection:(UITextView *)textView
In there, you can use the selectedRange poperty to get the selection. See this reference for details:
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITextView_Class/Reference/UITextView.html#//apple_ref/doc/uid/TP40006898-CH3-SW13
UITextField don't have delegate to get the selection range change. We can use KVO to observe selectedTextRange property of UITextfield.
[textField addObserver:self forKeyPath:#"selectedTextRange" options:NSKeyValueObservingOptionNew context:NULL];
Or create UITextField subclass and override setSelectedTextRange method.