I want to have a NSDictionary
that maps from UIView
s to something else.
However, since UIViews do not implement the NSCopying
protocol, I can't use them directly as dictionary keys.
I want to have a NSDictionary
that maps from UIView
s to something else.
However, since UIViews do not implement the NSCopying
protocol, I can't use them directly as dictionary keys.
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.
valueWithNonretainedObject
. –
Alexaalexander 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.
[NSMapTable weakToStrongObjectsMapTable]
–
Outspan 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]]
© 2022 - 2024 — McMap. All rights reserved.
UIView
instances. – AlexaalexanderUIView
they belong to. If you want to add data to aUIView
subclass, at some point you'll need a unique key per UIView. No? – AlexaalexanderCFDictionary
orNSHashMap
– Outspan