Search code examples
iosdelegateskeyboarduitextfieldfirst-responder

trying to use -(BOOL) textFieldShouldReturn:(UITextField *)textField; to make the keyboard go away in a subview


I have been trying to solve this for many hours. None of the posts I have found anywhere has provided a solution. The problem is the textField is in a subview and I cannot figure out how to make it respond to the Return button to hide the keyboard. Here is my view controller;

    #import <UIKit/UIKit.h>
#import "Combatant.h"
@interface ViewController : UIViewController <CombatantDelegate>


- (IBAction) addView;
- (IBAction) roll;

@end

    #import "ViewController.h"

static int startY = 60;

@interface ViewController ()
{
    NSMutableArray * customViews;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    customViews = [[NSMutableArray alloc] init];
}

- (IBAction) addView
{
    Combatant * thing = [[NSBundle mainBundle] loadNibNamed:@"Combatant" owner:nil options:nil][0];
    [self.view addSubview: thing];
    thing.center = CGPointMake(self.view.center.x, startY + customViews.count * thing.bounds.size.height);
    thing.combatDelegate = self;
    [customViews addObject:thing];
}

- (IBAction) roll
{
    for (Combatant * cust in customViews)
    {
        [cust rollWasTapped];
    }
}
@end

Here is my custom Class;

    #import <UIKit/UIKit.h>

@class Combatant;

@protocol CombatantDelegate <NSObject>

@end



@interface Combatant : UIView <UITextFieldDelegate>
{
    IBOutlet UITextField * name;
    IBOutlet UITextField * base;
    IBOutlet UITextField * die;
    IBOutlet UITextField * mod;
    IBOutlet UILabel * total;
    NSString *value;
    int dRoll;
    int cBase;
    int cMod;
}

@property (nonatomic, strong, readwrite) NSString *value;
@property (nonatomic, strong) IBOutlet UITextField * name;
@property (nonatomic, strong) IBOutlet UITextField * base;
@property (nonatomic, strong) IBOutlet UITextField * die;
@property (nonatomic, strong) IBOutlet UITextField * mod;
@property (nonatomic) IBOutlet UILabel * total;
-(void) rollWasTapped;
-(BOOL) textFieldShouldReturn:(UITextField *)textField;
@property (nonatomic, weak) id<CombatantDelegate> combatDelegate;
-(int) dieRoll;
-(int) convertBase;
-(int) convertMod;
@end

    #import "Combatant.h"

@implementation Combatant

@synthesize value;
@synthesize name;
@synthesize base;
@synthesize die;
@synthesize mod;
@synthesize total;

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {

    }
    return self;
}

-(BOOL) textFieldShouldReturn:(UITextField *)textField{
    [name resignFirstResponder];
    return YES;
}

- (void) rollWasTapped;
{
    int tot;
    tot = [self convertBase] + [self dieRoll] + [self convertMod];
    total.text = [NSString stringWithFormat:@"%i", tot];
    NSLog(@" totaling %i ", tot);
}

 -(int) dieRoll
 {
     int val = [die.text intValue];
     if (val <7 && val >0)
     {
         NSLog(@" %i die", val);
     } else {
         NSLog(@" there are no dice");
         val = 0;    }
     int d = 0;
     for (int i = 0; i < val; i++) {
         d = d + arc4random()%6 + 1;
     }
     dRoll = d;
     NSLog(@"%i die roll result = %i ", val, dRoll);
     return dRoll;
 }

 -(int) convertBase
 {
     cBase = [base.text intValue];
     NSLog(@"base = %i", cBase);
     if (cBase > -1 && cBase < 20)
     {
         return cBase;
     }
     return 0;
}

 -(int) convertMod
 {
    cMod = [mod.text intValue];
    NSLog(@"mod = %i", cMod);
    if (cMod > -20 && cMod < 20)
    {
        return cMod;
    }
    return 0;
 }

- (void) dealloc
{
    self.combatDelegate = nil;
}
@end

The code works perfect in every aspect except the keyboard hiding.


Solution

  • In the most basic sense, you want the currently active text field to resignFirstResponder. Your problem seems to be how to access that text field.

    edit: the below paragraphs jump topics quickly, as I'm detailing a few possible solutions instead of 1.

    Goal: Accessing the Text field. Solutions: 1) Create a property that exposes the text field on the custom class. 2) Create a function that calls the text field on the custom class.

    You have 4 properties exposing the text fields, assuming there are 4 text fields on each view?

    In that case, go with creating a function, and in that function, on the view, make sure it calls self.textField1 resign... blah, then self.textField2, just make sure it hits all of them.

    Currently you call name resignFirstResponder in the callback. But first let's backtrace a little bit. All textFields call textFieldShouldReturn on their delegate. So make sure all 4 of your text fields have the view as the delegate. Either in interface builder or code, self.name.delegate (or self.name.textFieldDelegate, I forgot exactly) = self; for each one.

    Also, remember to call "self.", "name" is something else. Actually self. accesses the property accessor. But the invisible variable it creates is called _name (same thing with an underscore at the beginning, this works for all properties).

    Now that we know all your fields are being called correctly, let's get back to the delegate. If you really do want the "currently active, returning" text field to resign its responder, the delegate passes in its object view the textField, so just remember to call resignFirst... on the currently returning field.

    [textField resignFirstResponder]
    

    Of course, that belongs in the delegate callback method, since textField does not exist otherwise, lest you access it by its name (self.name, or the others).

    Last but not least, put a button behind everything, but large enough to cover the screen, make it call a function. In that function, you can "splay" the call to every text field. Basically, make that function call the "hideTheKeyboard" or whatever you named it (the function that calls resignFirst... on all the fields within the custom view) on every custom view.

    It should look like your function with for( view in customViews) but instead of calling roll, it calls your custom function.

    Within that function, just because it is a possible solution, you can also do something like

    - (IBAction) roll
    {
        for (Combatant * cust in customViews)
        {
            [cust.name resignFirstResponder];
            [cust.die resignFirstResponder]; 
            // and on for the other 2
        }
    }
    

    But, as you can tell, the reason you might want that encapsulated into a function that you call is simply for less lines of code/simplicity. It is more important that you understand how and why you can do it either way than finding a solution that works. There are 3 solutions I've listed above (use a property, use a function to access the textField, or use the delegate).

    The simplest is the delegate, the most compact is the function, and the most widespread is the property. (There are also limitations to each one). I wanted to give a thorough understanding so you can see them.