Prevent users from modifying part of the text in SLComposeViewController - ios

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/"]];

Related

JSQMessagesViewController custom link

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

Return key pressed on UITextView

I have a UITextView which I am using as text entry in a chat application. When the user presses return then I want to do some action e.g. save the chat message.
I haven't been able to find a solution that allows me to do this (lots for TextFields but not for TextView).
Here is the solution I am trying at the moment, which seems to be the most obvious I can find, but it isnt working, in debug I see that the method isn't touched:
- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range replacementText:(NSString *)text {
if ([text isEqualToString:#"\n"]) {
NSLog(#"Return pressed");
} else {
NSLog(#"Other pressed");
}
return YES;
}
In my chat .h file:
#interface ChatTableViewController : UITableViewController <UITextViewDelegate>
and viewdidload .m file
- (void)viewDidLoad {
[super viewDidLoad];
enterText.delegate = self;
// more
}
Incidentally if there is a better field to use as input in a chat program than UITextView please let me know.
Many thanks
... but it isn't working, in debug I see that the method isn't touched
Your problem obviously is that the delegate method is not called. Fix the delegate and use the code you already have: it's good.
Try this way. No need to go with delegates.
Add this event responder at where you initialize the textView.
[theTextView addTarget:self
action:#selector(targetMethodToPerformCustomOperation)
forControlEvents:UIControlEventEditingDidEndOnExit];

Localization for UITextView in storyboard

So I have an application with different buttons, labels and some Text Views in storyboard where I entered the text directly in the storyboard. I enabled base localization and added a couple of languages.
This generated storyboards for Base (English) and the other languages with a list of items objectIDs.
I translated everything, and the labels and buttons (ALL OF THEM) work and show in the language I set the device to.
The text fields however keep showing the initial English text no matter which language I set...
Are there any extra steps involved for Text View?
So, I did some research, and it seems that in order for this to work correctly, the text for the UITextView needs to be set programmatically.
Source: Devforums.apple
Quote:
as I understand it, strings such as the text property of a text view/field have to be set in code using NSLocalizedString. The first 1/2 hour of WWDC 2013 video session #219 Making Your App World Ready covers this if you have the time to watch it
So, it seems that the workaround (if you don't want to set the text programmatically) is to convert the strings file into a storyboard before shipping the app. This does seem to work as intended and shows the UITextView properly localized.
EDIT: Found another workaround that allows to keep .strings file.
In - (void)viewDidLoad:
for(UIView* v in self.view.subviews)
{
if([v isKindOfClass:[UITextView class]])
{
UITextView* txv = (UITextView*)v;
NSString *loctxt = [txv.text stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
txv.text = NSLocalizedString(loctxt, #"");
}
}
This produces a Percent Escapes encoded string from whatever is inside the storyboard, like this:
Hello%20World
In your Localizable.strings file, you use the above as the key, and this will produce the localized text in the app at runtime for the selected locale, like this:
"Hello%20World" = "Hallo Welt";
The Percent escaping takes care of all escape characters in the base string.
As my comment on Dmitry's answer cannot be formatted nicely, I repeat this as an answer here. The Swift version of his solution looks like this:
override func viewDidLoad() {
super.viewDidLoad()
for view in self.view.subviews {
if let tv = view as? UITextView, ident = view.restorationIdentifier {
tv.text = NSLocalizedString("\(ident).text", tableName: "Main", comment: "")
}
}
// Do any additional setup after loading the view.
}
(Note that in Swift, NSLocalizedString replaces several Objective-C macros, one of them being NSLocalizedStringFromTable)
P.S.: Unfortunately, in iOS 10 this seems not to work any more. Instead, the call gives back the id that was supplied as first parameter (e.g. "abc-xy-pqr.text"). Any ideas?
If anyone is still interested, I have solved this problem a different way, this will allow you to still use the SAME .Strings file that is generated by Xcode for storyboards.
There are two parts to this solution:
In the .m file for your view add this code:
- (void)viewDidLoad
{
[super viewDidLoad];
for(UIView* view in self.view.subviews)
{
if([view isKindOfClass:[UITextView class]] && view.restorationIdentifier)
{
UITextView* textView = (UITextView*)view;
NSString *textViewName = [NSString stringWithFormat:#"%#.text",textView.restorationIdentifier];
textView.text = NSLocalizedStringFromTable(textViewName, #"Main", nil);
//change this to be the same as the name of your storyboard ^^^
}
}
// Do any additional setup after loading the view.
}
and in your storyboard in the identity inspector copy the "Object ID" to the "Restoration ID" field.
This will apply the new localized text to all of your UITextViews on screen load and will allow you to use the already generated strings files.
I made my own Categories for the components.
For example, a button:
#import <UIKit/UIKit.h>
#interface LocalizedButton : UIButton
#end
#import "LocalizedButton.h"
#implementation LocalizedButton
- (id)initWithCoder:(NSCoder *)aDecoder {
NSLog(#"Loading LocalizedButton: initWithCoder");
if ((self = [super initWithCoder:aDecoder])){
[self localizeButton];
}
return self;
}
- (id)initWithFrame:(CGRect)frame{
NSLog(#"Loading LocalizedButton: initWithFrame");
self = [super initWithFrame:frame];
if (self) {
[self localizeButton];
}
return self;
}
-(void) localizeButton{
self.titleLabel.adjustsFontSizeToFitWidth = YES;
NSString* text = NSLocalizedString(self.titleLabel.text, nil);
[self setTitle:text forState:UIControlStateNormal];
[self setTitle:text forState:UIControlStateHighlighted];
[self setTitle:text forState:UIControlStateDisabled];
[self setTitle:text forState:UIControlStateSelected];
}
#end
You can se the complete code on: https://github.com/exmo/equizmo-ios/blob/master/Quiz/LocalizedButton.m
The Swift solution of https://stackoverflow.com/users/1950945/stefan works for me on iOS 10.2 when I replace the "Main" with the correct id (e.g. "MainStoryboard") which references the localized file id.storyboard (e.g. "MainStoryboard.storyboard")

UITextView textViewDidChangeSelection called twice

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.

Show a single character as it is typed on a separate label?

I have a row of labels that have been programmatically instantiated, they are stored in an NSMutableArray. They don't currently contain any data. What I'm trying to do is make it so that when a user types in a character it is automatically displayed in the labels. I'm not sure how to do this. I know how to access the labels I have created [MyArray ObjectAtIndex:0] and so on, but how could I make it so that when a user types on the keyboard it formats the text (I have code for formatting) and then just appears on screen.
I need help putting each character on the screen as it is typed.
Any help would be greatly appreciated - I have a textfield (it's hidden) and the keyboard comes up by button. If that helps. :)
Thank you in advance :).
UITextField *tf;
[tf addTarget:self action:#selector(editingChanged:) forControlEvents:UIControlEventEditingChanged];
- (void)editingChanged:(UITextField *)textField {
_myHiddenLabel.text = textField.text;
}
You can add observer when Text inside UITextField changes and then access your labels and add text to it...
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(changeLabelsMethod:)
name:UITextFieldTextDidChangeNotification
object:myHiddenTextField];
}
-(void)changeLabelsMethod:(UITextField*)txtField
{
Static int i=0;
if(i<[MyArray count])
{
UILabel *lbl=[MyArray ObjectAtIndex:i];
lbl.text=txtField.text;
}
else
return
i++;
}
EDIT: Refer Eugene's answer for right approach
You can get notified every time a character is typed if you set your viewController as delegate to your hidden textField and implementing UITextFieldDelegate Protocol
- (IBAction)textFieldValueChanged {
NSString *strLastChar = [txtSearch.text substringFromIndex:txtSearch.text.length-1];
UILabel *lblCurrent = [arrSearch objectAtIndex:intCurrentLblNo];
[lblCurrent setText:strLastChar];
intCurrentLblNo++;
}
take intCurrentLblNo as global variable and set intCurrentLblNo = 0; in viewdidload method
and set it...
[txtSearch addTarget:self action:#selector(textFieldValueChanged) forControlEvents:UIControlEventValueChanged];
in viewdidload method

Resources