UIView as dictionary key?
Asked Answered
C

7

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.

Citizen answered 7/6, 2010 at 19:4 Comment(10)
Relevant: #1498122Mucronate
This sounds like a really really bad idea.Circumstantial
Only if you're not aware of the fact that the data might become garbage (as the accepted answer points out).Citizen
@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.Alexaalexander
@Yar like, perhaps, associated objects?Circumstantial
@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? compileyouidontevenknowyou.blogspot.com/2011/09/…Alexaalexander
@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?Alexaalexander
@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: compileyouidontevenknowyou.blogspot.com/2012/06/…Alexaalexander
Which eliminates the need to use UIView as a dictionary key (since if you have the key, you have the associated objects).Alexaalexander
You could also use CFDictionary or NSHashMapOutspan
H
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.

Heave answered 7/6, 2010 at 19:9 Comment(3)
Fabulous! I was about to use an NSNumber with the UIView's hash as an int. This is much nicer, I think.Alexaalexander
Now with ARC you must use valueWithNonretainedObject.Alexaalexander
@Yar Here is the actual code using suggestions by luvieere and yourself: https://mcmap.net/q/498770/-uiview-as-dictionary-keyWartime
H
16

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]];
Heinrik answered 8/7, 2012 at 21:53 Comment(2)
How can I check if the view was deallocated or not?Simonasimonds
I don't get it why to store an NSString as value and a UIView as key. Also, extracting it does not make sense if the caller doesn't have an instance of the view.Polytechnic
C
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.

Caracaraballo answered 8/7, 2012 at 22:4 Comment(0)
M
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.

Moeller answered 9/12, 2014 at 22:6 Comment(1)
This is a great solution!Izard
B
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.

Brucine answered 18/10, 2012 at 23:48 Comment(0)
C
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.

Chromogenic answered 4/3, 2014 at 0:42 Comment(1)
NSMapTable also provides this. c.f. [NSMapTable weakToStrongObjectsMapTable]Outspan
W
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]]
Weymouth answered 21/7, 2017 at 8:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.