I have custom UIToolbar's that sit ontop of my keyboard when it displays and that I use to insert pre-formatted text. My problem is that I have one UITextView and two UITextField's that are potential users of my pre-formatted text.
When the buttons call an IBAction, how can I tell which element has focus and is the firstResponder?
Attempt #2 at asking my question smartly:
I need to call [textElement selectedRange]; on the UITextField or UITextView that has focus. How can I declare textElement in a way that it doesn't matter which of the view UIText* classes it is?
If I do the following...
UIView *textElement = [self my_method_of_getting_first_responder];
NSRange range = [textElement selectedRange];
I of course get the
UIView does not declare selector 'selectedRange'
Thanks!
If I understood question, you want to click some button, and then get the link to you currently active text field? Then you have to go with category on UIView:
#implementation UIView (FindFirstResponder)
- (BOOL)findFirstResponder
{
if (self.isFirstResponder) {
return YES;
}
for (UIView *subView in self.subviews) {
if ([subView findFirstResponder]) {
return YES;
}
}
return NO;
}
#end
You can call this category method on UIWindow from you view controller. To get window use UIWindow *window= self.view.window;
Related
I have a UIView which draws a signature. The signature is inputed via a custom inputView on the signature view.
When drawing the signature view, I use the fact that it's the first responder to highlight the view as being edited, and I also override the resignFirstResponder method to work out when to stop showing it as being edited.
So the code looks something like this:
#implementation SignatureView
-(BOOL) becomeFirstResponder {
BOOL result = [super becomeFirstResponder];
[self showEditingMode];
return result;
}
-(BOOL) resignFirstResponder {
BOOL result = [super resignFirstResponder];
[self showViewingMode];
return result;
}
-(UIView *) inputView {
if (!keyboard)
keyboard = [[SignatureKeyboardView alloc] initWithStuff:stuff....];
return keyboard;
}
#end
The problem I'm having is that on iOS 11, the resignFirstResponder method is no longer getting called. On previous versions of iOS, it used to get called and I could then change the UI to show that it's no longer being edited.
This ONLY HAPPENS when the UIScrollView is set to dismiss the keyboard when dragged, and the user drags the UIScrollView.
If the user instead taps another UIView that can become a first responder e.g. a UITextField, then resignFirstResponder gets called.
Am I missing something that changed in iOS11 or have I hit a bug?
I am developing an app that supports editing attributedText in a UITextView. To provide the tools to the user for formatting their input, I am using an inputAccessoryView to augment the keyboard with options like bullet list, numbered list, indent, outdent, font controls (bold, underline, increase fontsize, decrease font size), etc. This is too much to fit on the inputAccessoryView so I am looking to use the UIMenuController to provide a mechanism to provide more space for the user to make their intentions known.
So, I have a inputAccessoryView with a 'listAccessory' button. When it is pressed, I wanted to show a UIMenuController with four options (bullet, number, increase indent, decrease indent). But when I show this menu, it ALSO includes 'select', 'selectAll' and 'paste'.
I do not have any of these methods (select:, selectAll:, or paste: as defined in the UIResponderStandardEditActions informal protocol) defined in my view. I have defined canPerformAction:withSender: and only respond with 'YES' for my selectors.
- (BOOL) canPerformAction:(SEL)selector withSender:(id) sender
{
DDLogInfo(#"canPerformAction: %#", NSStringFromSelector(selector));
if (selector == #selector(formatAsBulletList:)) return YES;
if (selector == #selector(formatAsNumberedList:)) return YES;
if (selector == #selector(formatIncreaseIndent:)) return YES;
if (selector == #selector(formatDecreaseIndent:)) return YES;
return NO; // return [super canPerformAction:selector withSender:sender];
}
When I log the selectors being called in this code, I don't see any request for 'select:', 'selectAll:', or 'paste:', so I believe that the UIMenuController code is testing for those methods with direct calls to canPerformSelector() against the class.
Since I don't implement those functions in my viewController (derived from UITableViewController), I can only believe surmise that the UIMenuController is looking up the responder chain and seeing that the responder that initiatated the keyboard initially is a UITextView, which DOES support select, selectAll, and paste.
So I have a couple of questions:
1) is my understanding of the situation right?
2) how do I force those menu items to not appear? Can I somehow temporarily break the responder chain without dismissing the keyboard?
Cool question. The problem is as you understand it .The UITextView IS the first responder when you try to invoke your menu so it populates the menu with the Select and Select All actions
A solution is to subclass UITextView and add an extra property which allows you to block the items briefly.
I tried this and it works on UITextField as its what i had to hand but theres no reason to believe it won't work on UITextView
Subclass your view lightly.
#interface CharlieDevTextView : UITextView
#property BOOL blockActionMenu;
#end
And
#implementation CharlieDevTextView
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if (self.blockActionMenu) {
return NO;
}
return [super canPerformAction:action withSender:sender];
}
#end
then when you construct your menu (assuming you have an IBOutlet to the textview)
-(void)yellowMellow:(id)sender {
}
-(void)createMenuForButton:(UIButton *)sender
{
UIMenuItem *newInstanceItem = [[UIMenuItem alloc] initWithTitle:#"Woot" action:#selector(yellowMellow:)];
[UIMenuController sharedMenuController].menuItems = #[newInstanceItem];
CGPoint apoint = sender.center;
self.charlieTextView.blockActionMenu = YES;
[[UIMenuController sharedMenuController] setTargetRect:CGRectMake(apoint.x,apoint.y, 0, 0) inView:sender.superview];
[[UIMenuController sharedMenuController] setMenuVisible:YES animated:YES];
self.charlieTextView.blockActionMenu = NO;
}
Alternatively listen to the UIMenuControllerWillShowMenuNotification and UIMenuControllerDidHideMenuNotification for switching blocking on and off.
and possibly to make it slightly less ugly create a delegate rather than a property. Your view controller will be the delegate and predicate on whether it is about to show the menu or noticed that the menu has been dismissed.
Essentially same effect , different pattern.
#protocol CharlieTextViewMenuDelegate <NSObject>
-(BOOL)shouldBlockMenu;
#end
#interface CharlieTextView : UITextView
#property (nonatomic,weak) id< CharlieTextViewMenuDelegate> menuDelegate;
#end
#implementation CharlieDevTextView
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender {
if ([self.menuDelegate shouldBlockMenu]) {
return NO;
}
return [super canPerformAction:action withSender:sender];
}
#end
Is it possible to dismiss the keyboard when you have MULTIPLE UITextFields ? If so how ?
As a side note, do I have to dismiss the keyboard for Each and Every field or can it be done globally ? Oh and it would be super cool if I don't have to touch the DONE button, I'd ideally like a solution that where the user touches anything BUT the field in question and the keyboard automagically disappears...
Oh and if you'd be so kind step by step instructions.
I should have added that I have a method already to resign the keyboard....
However, it only runs when my form is submitted! (see method below)
My question is how to the keyboard to hide/dismiss without having to jump thru so many damned hoops! You'd figure after 6 years, a mature operating system would have a way to GLOBALLY hide the keyboard....NOT!
Ok, enough whining....
- (void)hideKeyboard {
[self.dancePlace resignFirstResponder];
[self.danceGate resignFirstResponder];
[self.danceTerminal resignFirstResponder];
[self.danceText resignFirstResponder];
[self.danceDate resignFirstResponder];
[self.danceStyle resignFirstResponder];
[self.danceTimeOut resignFirstResponder];
}
And this is called when my button is submitted....
- (IBAction)addListingPressed:(id)sender {
// NSLog(#"BUTTON PRESSED");
[self hideKeyboard];
[self valuesAdded];
}
My question, assuming anyone can answer this...and I suspect not, is there a way to globally hide the keyboard if the following conditions are MET: 1.) the user taps OUT of any one of the existing fields, 2.) presses anywhere else on the screen. 3.) Is no more than a line or two in the existing viewcontroller.m file. 4.) I don't have to add a confusing button on the viewcontroller. (any time I have to add outlets, the damned thing is crashing on me...and then nastiness happens, and really...remember I am JUST a beginner, and its very confusing to read that I have to place this here and that there...oy. Simple folks, simple. I'm not looking for elegant solution, just so that it works.
I have a super class that all my view controllers inherit from. In that class I have this code.
MySuperViewController.h
#import <UIKit/UIKit.h>
#interface MySuperViewController : UIViewController
#property(strong, nonatomic) UITapGestureRecognizer *backgroundTapGestureRecognizer;
#end
MySuperViewController.m
- (void)viewDidLoad{
//add a tap gesture recognizer to capture all tap events
//this will include tap events when a user clicks off of a textfield
self.backgroundTapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(onBackgroundTap:)];
self.backgroundTapGestureRecognizer.numberOfTapsRequired = 1;
self.backgroundTapGestureRecognizer.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:self.backgroundTapGestureRecognizer];
}
- (void)onBackgroundTap:(id)sender{
//when the tap gesture recognizer gets an event, it calls endEditing on the view controller's view
//this should dismiss the keyboard
[[self view] endEditing:YES];
}
I have the UITapGestureRecognizer as a public property, so I can override it if I need to.
subclass
MyViewController.h
#import <UIKit/UIKit.h>
#import "MySuperViewController.h"
#interface MyViewController : MySuperViewController<UIGestureRecognizerDelegate>
#end
MyViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
//You don't always want the keyboard to be dismissed, so you tie into the gesture recognizer's delegate method
//By doing this, you can stop the endEditing call from being made
[self.backgroundTapGestureRecognizer setDelegate:self];
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
//touch.view is the view that recieved the touch
//if this view is another textfield or maybe a button, you can return NO and the endEditing call won't be made
if (touch.view == self.myViewThatShouldNotBeBlocked) {
return NO;
}
//if you want the gesture recognizer to accept the event, return yest
return YES;
}
I uploaded an example project to github.
https://github.com/JeffRegan/KeyboardBeGone
RDVKeyboardAvoiding is a scroll view with a tap gesture recognizer, designed for multiple textViews/textFields. It keeps track of the active view and removes a lot of boilerplate code.
tap anywhere outside the textField .. it will hide it..
[self.view endEditing:YES];
There are couple of other ways to do it.
[myEditField resignFirstResponder];
[myEditField endEditing];
[parentView endEditing];
If you dont wont to do so many things and simply want to dismiss keyboard than give iboutlet to each of your text filed to following method..
-(IBAction)hidekeyboard:(id)sender
{
[sender resignFirstResponder];
}
Yes, you only have to dismiss it for the one that is currently being edited.
In order to know which one is being edited, you can check the -(BOOL)isFirstResponder property, which will return YES if it is the first responder (the one being edited) or NO if it is not. Once you know which one is the first responder you can call -(void)resignFirstResponder on that one to get rid of the keyboard.
For example, if you have a method called -(void)aMethod that you want to dismiss the current view controller and you have an array of textViews called textArray, you could do a little loop such as:
-(void)aMethod {
for (UITextField *text in self.textArray) {
if ([text isFirstResponder]) [text resignFirstResponder];
return;
}
}
This way, you can have a variable number of textFields and it will still work.
If you only have one or two textFields and you do not want to create an Array object, you could do (assuming the fields are named text1 and text2:
-(void)aMethod {
if ([text1 isFirstResponder]) [text1 resignFirstResponder];
else if([text2 isFirstResponder]) [text2 resignFirstResponder];
}
Also, to make things easier for the future you could create a category method for UIView (which is what I do) to get the current first responder if it exists as a subview of that view:
#implementation UIView (GetFirstResponder)
- (UIView *)getFirstResponder {
if ([self isFirstResponder]) return self;
else {
for (UIView *subview in self.subviews) {
UIView *firstResponder = [subview getFirstResponder];
if (firstResponder) return firstResponder;
}
}
return nil;
}
You can put this method on the top of any file that you want to call it from, or create a separate file for it and import it.
Once you have this method, you can call:
- (void)aMethod {
UIView *view = [self.view getFirstResponder];
if (view) [view resignFirstResponder];
}
[superview endEditing:YES]; // superview can be the view controller's view property.
I'd like to dismiss the keyboard with a text field using
- (BOOL)textFieldShouldReturn:(UITextField *)textField {, but I need to do the same with a text view, so I'll use - (BOOL)textViewShouldReturn:(UITextView *)textView {. Is it possible to put them togheter in the same AppDelegate?
Thank you.
Yes, you can have two delegates
#interface ViewController : UIViewController<UITextFieldDelegate,UITextViewDelegate>
Unfortunately
The protocol UITextViewDelegate does not have, something like this.
- (BOOL)textViewShouldReturn:(UITextView *)textView {
UITextViewDelegate Protocol,
EDIT 1 :
Button press event to hide the keyboard.
-(IBAction) yourButtonPressed:(id)sender;{
for(UIView *v in self.view.subviews){
if([v isKindOfClass:[UITextField class]] || [v isKindOfClass:[UITextView class]]){
if([v isFirstResponder]){
[v resignFirstResponder];
break;
}
}
}
}
I don't fully understand your question...
Basically you will do the following:
implement the UITextFieldDelegate and UITextViewDelegate in your
viewController
implement the methods of that delegates, you need/want
resignFirstResponder / endEditing of the textField / textView, where
ever you want
Just subscribe to the delegates from your viewController and make sure to set the delegate on those objects to your viewController.
.h
#interface YourViewController : UIViewController <UITextFieldDelegate, UITextViewDelegate>
.m
someTextField.delegate = self;
someTextView.delegate = self;
From the sound of it you just need to tie into the actions of the textField and textView. Create an IBAction and tie it to what you'd like. Then you can resignFirstResponder from that IBAction.
Use this action for both your textField and textView
- (IBAction)lowerTheText:(id)sender
{
[sender resignFirstResponder];
}
Yes, with a UITextView it's tricky. As I say in my book...
http://www.apeth.com/iOSBook/ch23.html#_uitextview
...on the iPad, the problem of dismissing the keyboard doesn't arise because the user can dismiss it with the button in the lower right corner of the keyboard. So this leaves only the iPhone. You will typically have an interface such that there is a Done button or similar. Look at how the Notes app solves this, for example.
The process itself is just the same: call endEditing: on the superview and whoever is first responder will cease being first responder and the keyboard will retire.
Yes, you can use textfield delegate and textview delegate in the same application.
TextField:
#interface ViewController : UIViewController<UITextFieldDelegate,UITextViewDelegate>
// This allocates the textfield and sets its frame (or) you can use interfaceBuild
UITextField *textField = [[UITextField alloc] initWithFrame:
CGRectMake(20, 50, 280, 30)];
textField.delegate=self;
// This method enables or disables the processing of return key
-(BOOL) textFieldShouldReturn:(UITextField *)textField{
[textField resignFirstResponder]; // this is event for hide keyboard.
return YES;
}
TextView:
// init
UITextView *textView = [[UITextView alloc] initWithFrame:
CGRectMake(20, 50, 280, 30)];
- (void)textViewDidChangeSelection:(UITextView *)textView
{
[textField resignFirstResponder]; // this is event for hide keyboard.
}
In my iOS app I have several UIElements that can process user input: textfields, editable webviews, etc. each time I write something into these UIElements the keyboard (obviously) will come up. Before it happens I can catch this event by observing the UIKeyboardWillShowNotification.
I would like to know what's the way to find out which UIElement invoked this action.
Thanks for your help!
The keyboard is launched when the view tapped by a user is set as FirstResponder, so I think this question is the equivalent of saying how do I get the current first responder when UIKeyboardWillShowNotification is received?.
The answer to that question by Thomas Muller was to use a class extension along the lines of:
#implementation UIView (FindFirstResponder)
- (UIView *)findFirstResponder
{
if (self.isFirstResponder) {
return self;
}
for (UIView *subView in self.subviews) {
UIView *firstResponder = [subView findFirstResponder];
if (firstResponder != nil) {
return firstResponder;
}
}
return nil;
}
#end
So I think you could use that inside your handler for UIKeyboardWillShow to figure out what caused it.
I think the section 4 (Moving Content That Is Located Under the Keyboard) of this document can give you a hint about knowing wich element has the keyboard.
http://developer.apple.com/library/ios/#DOCUMENTATION/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/KeyboardManagement/KeyboardManagement.html#//apple_ref/doc/uid/TP40009542-CH5-SW1