Is there a way to automatically tie a UITextField

2019-02-24 06:55发布

问题:

I'm building up a view controller where I'm adding a bunch of UITextFields to my view programmatically. I want to pre-populate the textfields with some text that I'm loading from CoreData, but allow the user to go in and change that text if they want. I then need to go back and save that new text back to CoreData again, as well as perform calculations using the new value. The saving and loading itself isn't a problem, but I'm looking for a good way to track what the user has changed.

In the past, I've done this with the .tag property of the UITextField and the textFieldDidEndEditing: delegate method. The problem is I'm going to have dozens of textfields in this view - enough so that I can't really remember what .tag belongs to what variable in my model. Having to go back to look up what tag I assigned when I created it is going to be a pain, and the whole thing feels really error prone. Additionally, I'm going to be creating the textfields in multiple different loops, so getting a unique tag for each textfield when I create it will be difficult (maybe impossible?).

What I'm looking for is a way to "tie" the UITextField to a variable in my model when I create it and just know from that point on, whenever the user updates that textfield, the value I specify in my model will be instantly updated to be used in future calculations, and saving back to CoreData when the user leaves the screen. Is there a way to do this?

EDIT 1: Another thing to note is that my model itself is kinda complex. I have several different custom NSObject subclasses that are all holding different pieces of my data. Some of the objects have properties that are themselves other custom objects. I also have dictionaries where the values will be instances of some of the custom objects.

As an example, I might have these objects:

  • MySmallObject1, which has properties name,size, and value (all NSStrings)
  • MySmallObject2, which has properties date and cost (an NSDate and an NSNumber)
  • MyBigObject, which has properties box1 and box2 (which are MySmallObject1 and MySmallObject2 instances, respectively)
  • theDict, which is an NSDictionary instance that has MyBigObjects as values

So when I am building my textfields, I might actually be going down a tree that would look something like:

for (NSString *key in [theDict allKeys])
{
    UITextField *txt = [[UITextField alloc] initWithFrame:...];
    txt.text = [[(MyBigObject *)[theDict objectForKey:key] box1] name];
    [self.view addSubview:txt];
}

In this case, I need to know when the user changes the text in any of the txt and update the appropriate value through the tree.

EDIT 2: While trying to implement @Till's suggestion in the comments, I started reading a tutorial on pointers in C. It seems like this is the way I need to go, but I'm struggling to get the & and * from the C world to play nicely with the NSObject subclasses I mention in my last EDIT from the Objective-C world. To get @Till's suggestion to work, I think I need to somehow store the memory location of my value up that complicated object tree. So my code block from the last EDIT would become:

.h
@property (nonatomic, strong) NSMutableDictionary *tagDict;

.m
for (NSString *key in [theDict allKeys])
{
    UITextField *txt = [[UITextField alloc] initWithFrame:...];
    txt.text = [[(MyBigObject *)[theDict objectForKey:key] box1] name];
    txt.tag = globalCounter;
    [tagDict addObject:[[(MyBigObject *)[theDict objectForKey:key] box1] name] forKey:[NSString stringWithFormat:@"%d",globalCounter]];
    globalCounter ++;
    [self.view addSubview:txt];
}

Where globalCounter is an int that I'm incrementing every time I put something into tagDict so I can keep it unique. I guess in this case I could just simply be using an NSArray and it would work just as well, and probably look a little cleaner. My plan is to use tagDict in my testFieldDidEndEditing like:

- (void) textFieldDidEndEditing:(UITextField *)textField
{
    *[tagDict objectForKey:[NSString stringWithFormat:@"%d",textField.tag]] = textField.txt;
}

This throws an error for Assigning to 'id' from incompatible type 'NSString *'. I'm not really sure what that means. Is there a way I can use a "dereferencing operator" to change the value of the item I am pointing to in my dictionary?

throws an error Address expression must be an lvalue or a function designator. I'm still a little fuzzy on how this pointer stuff would work in the Objective C world, and I think there's something I'm not fully understanding.

回答1:

You can use UITextField+blocks it may be less entangled. And it will be much easier if objects have similar interface or implement one protocol with method like setText: .



回答2:

You could use your original approach with the .tag , but use typedefs.

typedef enum TFTypes {
    TFModalType1,
    TFModalType2,
    TFModalType3,
    TFModalType4,
    TFModalType5,
    ...
} TFType;


回答3:

I ended up doing largely what I think @Till was trying to suggest. I added an NSMutableArray *tieArray and an int tieCounter as @properties to my view controller. My code for building the UITextFields from the first code block in my OP is now:

for (NSString *key in [theDict allKeys])
{
    UITextField *txt = [[UITextField alloc] initWithFrame:...];
    txt.text = [[(MyBigObject *)[theDict objectForKey:key] box1] name];
    [self.view addSubview:txt];

    txt.tag = self.tieCounter;
    self.tieCounter ++;
    [self.tieArray addObject:[[(MyBigObject *)[theDict objectForKey:key] box1] name]];
}

Then in my textFieldDidEndEditing

- (void) textFieldDidEndEditing:(UITextField *)textField
{
    if ([tieArray objectAtIndex:textField.tag] isKindOfClass:[NSMutableString class]])
    {
        [(NSMutableString *)[tieArray objectAtIndex:textField.tag] setString:textField.text];
    }
    else if (//...More conditions to set other variable types with the syntax as needed)
}

Note that for this to work, all the properties of MySmallObject1 and MySmallObject2 need to be covered by a isKindOfClass: check, and the actual syntax will vary a little depending on what class that property is.

I haven't been able to test this yet (and my app probably won't be in a testable state for quite some time), but it makes sense to me, and it's not throwing any errors or warnings. If I have problems with it when I actually run it I'll update here.