27

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.

mrueg
  • 8,185
  • 4
  • 44
  • 66
  • 1
    Relevant: http://stackoverflow.com/questions/1497622/nsmanagedobject-as-nsdictionary-key – kennytm Jun 07 '10 at 19:10
  • This sounds like a really really bad idea. – Dave DeLong Jun 07 '10 at 19:56
  • Only if you're not aware of the fact that the data might become garbage (as the accepted answer points out). – mrueg Jun 08 '10 at 10:21
  • @Dave DeLong, it might be a bad idea, but sometimes your only other option is to somehow mess with the UIView class itself to store state for a group of `UIView` instances. – Dan Rosenstark Sep 19 '11 at 00:19
  • 1
    @Yar like, perhaps, associated objects? – Dave DeLong Sep 19 '11 at 00:39
  • @Dave DeLong, I'm not saying it's a good idea, but I basically had to store information about UIViews today. How else to do it if not with a keyed dictionary? http://compileyouidontevenknowyou.blogspot.com/2011/09/uiviewautoresizing-has-limits.html – Dan Rosenstark Sep 19 '11 at 01:46
  • @Dave DeLong, if you have associated objects, at some point you need to figure out which `UIView` they belong to. If you want to add data to a `UIView` subclass, at some point you'll need a unique key per UIView. No? – Dan Rosenstark Sep 19 '11 at 05:09
  • @DaveDeLong my apologies, I had no idea what you were talking about at this time. I have since started to use associated objects as I show here: http://compileyouidontevenknowyou.blogspot.com/2012/06/adding-properties-to-class-you-dont.html – Dan Rosenstark Jul 11 '12 at 13:03
  • Which eliminates the need to use UIView as a dictionary key (since if you have the key, you have the associated objects). – Dan Rosenstark Jul 11 '12 at 13:50
  • 1
    You could also use `CFDictionary` or `NSHashMap` – nielsbot May 12 '14 at 21:06

7 Answers7

30

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.

luvieere
  • 37,065
  • 18
  • 127
  • 179
17

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]];
Community
  • 1
  • 1
Rok Strniša
  • 6,781
  • 6
  • 41
  • 53
5

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.

jscs
  • 63,694
  • 13
  • 151
  • 195
4

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.

Community
  • 1
  • 1
1

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.

Cody Poll
  • 7,690
  • 3
  • 27
  • 22
0

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.

Eliot
  • 5,450
  • 3
  • 32
  • 30
0

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]]
rpstw
  • 1,582
  • 14
  • 16