Pergunta

I want to have a NSDictionary that maps from UIViews to something else.

However, since UIViews do not implement the NSCopying protocol, I can't use them directly as dictionary keys.

Foi útil?

Solução

You can use an NSValue holding the pointer to the UIView and use this as key. NSValues are copyable. but, if the view is destroyed, the NSValue will hold a junk pointer.

Outras dicas

Here is the actual code (based on the answer by luvieere and further suggestion by Yar):

// create dictionary
NSMutableDictionary* dict = [NSMutableDictionary new];
// set value
UIView* view = [UILabel new];
dict[[NSValue valueWithNonretainedObject:view]] = @"foo";
// get value
NSString* foo = dict[[NSValue valueWithNonretainedObject:view]];

Although this isn't really what they're intended for, you could whip up a functional dictionary-like interface using Associative References:

static char associate_key;
void setValueForUIView(UIView * view, id val){
    objc_setAssociatedObject(view, &associate_key, val, OBJC_ASSOCIATION_RETAIN);
}

id valueForUIView(UIView * view){
    return objc_getAssociatedObject(view, &associate_key);
}

You could even wrap this up in a class ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable*; in that case you might want to retain the views that you use as keys.

Something like this (untested):

#import "ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable.h"
#import <objc/runtime.h>

static char associate_key;

@implementation ThingWhatActsLikeADictionaryButWithKeysThatArentCopyable

- (void)setObject: (id)obj forKey: (id)key
{
    // Remove association and release key if obj is nil but something was
    // previously set
    if( !obj ){
        if( [self objectForKey:key] ){
            objc_setAssociatedObject(key, &associate_key, nil, OBJC_ASSOCIATION_RETAIN);
            [key release];

        }
        return;
    }

    [key retain];
    // retain/release for obj is handled by associated objects functions
    objc_setAssociatedObject(key, &associate_key, obj, OBJC_ASSOCIATION_RETAIN);
}

- (id)objectForKey: (id)key 
{
    return objc_getAssociatedObject(key, &associate_key);
}

@end

*The name may need some work.

Provided you don't need to support before iOS 6, NSMapTable (suggested by neilsbot) works well because it can provide an enumerator over the keys in the collection. That's handy for code common to all of the text fields, like setting the delegate or bi-directionally syncing the text values with an NSUserDefaults instance.

in viewDidLoad

self.userDefFromTextField = [NSMapTable weakToStrongObjectsMapTable];
[self.userDefFromTextField setObject:@"fooUserDefKey" forKey:self.textFieldFoo];
[self.userDefFromTextField setObject:@"barUserDefKey" forKey:self.textFieldBar];
// skipped for clarity: more text fields

NSEnumerator *textFieldEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [textFieldEnumerator nextObject]) {
    textField.delegate = self;
}

in viewWillAppear:

NSEnumerator *keyEnumerator = [self.userDefFromTextField keyEnumerator];
UITextField *textField;
while (textField = [keyEnumerator nextObject]) {
    textField.text = [self.userDefaults stringForKey:[self.textFields objectForKey:textField]];
}

in textField:shouldChangeCharactersInRange:replacementString:

NSString *resultingText = [textField.text stringByReplacingCharactersInRange:range withString:string];
if(resultingText.length == 0) return YES;

NSString *preferenceKey = [self.textFields objectForKey:textField];
if(preferenceKey) [self.userDefaults setString:resultingText forKey:preferenceKey];
return YES;

And now I will go cry, because I implemented all of this before realizing that my iOS 5.1-targeted app can't use it. NSMapTable was introduced in iOS 6.

Rather than store a pointer to the view and risk the garbage issue, just give the UIView a tag and store the tag's value in the dictionary. Much safer.

I'm using a simple solution under ARC provided by Objective-C++.

MyClass.mm:

#import <map>

@implementation MyClass
{
    std::map<UIView* __weak, UIColor* __strong> viewMap;
}

- (void) someMethod
{
    viewMap[self.someView] = [UIColor redColor];
}

In this example I am getting stronger type checking by making all the values have to be a UIColor* which is all I needed this for. But you could also use id as the value type if you want to allow any object as the value, ex: std::map<UIView* __weak, id __strong> viewMap; Likewise for keys: id __weak, id __strong> viewMap;

You can also vary the __strong and __weak attributes as needed. In my case, the views are already retained by the view controller that I use this in, so I saw no need to take a strong pointer to them.

a simple solution when you just want UIView as key occasionally,I use it to store UILabel and UIColor

NSArray<UIView *> *views = @[viewA,viewB,viewC,viewD];
NSArray *values = @[valueA,valueB,valueC,valueD];

for(int i = 0;i < 4;i++) {
    UIView *key = views[i];
    id value = values[i]
    //do something
}

id value = values[[views indexOfObject:key]]
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top