Saving an array object as a string within a picker view? - iphone

So I am working with a pickerview and need a string to equal one of several things that I have stored in an array. The pickerview code for the components looks like this.
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
switch (row) {
case 0:
image.image = image1;
email = EmailArray[1];
break;
case 1:
image.image = image2;
email = EmailArray[3];
break;
case 2:
image.image = image3;
email = EmailArray[5];
break;
}
NSLog(#"%#", email)
}
This code works just fine as far as I can tell. The NSLog here returns the correct email every time. The user then presses a button that utilizes the "email" string. Here is the first part of that code.
- (IBAction)sendFeedback:(id)sender
{
NSLog(#"%#", email);
}
Only here the NSLog does not return the correct email. It just freezes the program and gives me a threading error that points at that NSLog. Am I not passing the array object to the string correctly?
If I change the code to this it works just fine.
switch (row) {
case 0:
image.image = image1;
email = #"testEmail#gmail.com";
break;
After this both NSLogs will display the test email.
Please help me figure out this problem. If you need more information just ask.
EDIT
Here is the beginning of my ViewController.h file.
#interface ViewController : UIViewController
<UIPickerViewDataSource, UIPickerViewDelegate, UIImagePickerControllerDelegate> {
NSMutableArray *EmailArray;
}
Here is where email is declared in the ViewController.h.
#implementation ViewController
NSString *email;
The error I get looks like this.
Thread 1:EXC_BAD_ACCESS(code=2, address=0x14)

You should not declared email as an instance var. An NSString must be declared as a property with copy modifier
#property (copy, nonatomic) NSString* email;

Your variable is not retained in memory, hence the crash.
You have two options
Add [email retain] after you assign it
Enable ARC and it will retain that variable for you.

Related

UIViewController may not respond to methodName:::

I'm trying to use methods to pass values from text fields in one view to corresponding text fields in another view. The action valuesChanged is wired to the event "Editing Did End" on the text fields. I tried using "Value Changed" with similar results (it doesn't work.). The only warning I receive is in a method call.
Here's the code for the VolumeViewController:
#implementation VolumeViewController
#synthesize boxWidth;
#synthesize boxHeight;
#synthesize boxLength;
#synthesize sphereRadius;
#synthesize boxResult;
#synthesize sphereResult;
#synthesize areaViewController;
-(IBAction)valuesChanged:(id)sender
{
NSString *length = boxLength.text;
NSString *width = boxWidth.text;
NSString *radius = sphereRadius.text;
NSLog(#"length: %#", length);
NSLog(#"width: %#", width);
NSLog(#"radius: %#", radius);
[areaViewController changeValues:width :length :radius];
}
-(void)changeValues:(NSString*)widthString :(NSString*)lengthString :(NSString*)radiusString
{
boxWidth.text = widthString;
boxLength.text = lengthString;
sphereRadius.text = radiusString;
}
As you can see, the valuesChanged action instantiates three NSString objects, each corresponding to the text properties of a text field. These strings are then passed to the changeValues method of the areaViewController. The areaViewController's changeValues method is identical to the volumeViewController's method, other than the text field names being different. However, the NSLog commands that I have never show anything in the console, so it's obvious that despite my text fields being connected to the action, it is not being called. What do I need to fix?
The warning is given after the method call "[areaViewController changeValues:width :length :radius], and it reads 'UIViewController' may not respond to '-changeValues:::'
Define the method it in your header.
What you can do to "declare" it in .m an not in .h (because it's kind of a private method for you, and it should not visible from outside) is add this before #implementation VolumeViewController :
#interface VolumeViewController()
- (void)changeValues: : : ;
#end
EDIT: It's better to name all the arguments in your method name. The signature should be:
-(void)changeWidth:(NSString*)widthString lenght:(NSString*)lengthString radius:(NSString*)radiusString;
This is more readable when you use it:
[areaViewController changeWidth:#"4" length:#"2" radius:#"6"];
You are calling changeValues on areaViewController, but you have defined that method for the VolumeViewController class
Try changing [areaViewController changeValues:width :length :radius];
to [self changeValues:width :length :radius];

UIView as dictionary key?

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.
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.
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]]

iphone sdk - problem pasting into current text location

I'm trying to paste text right into where the cursor currently is. I have been trying to do what it says at:
- http://dev.ragfield.com/2009/09/insert-text-at-current-cursor-location.html
The main deal is that I can't just go textbox1.text (etc) because the textfield is in the middle of a custom cell. I want to just have some text added to where the cursor is (when I press a custom key on a keyboard).
-I just want to paste a decimal into the textbox...
The error I get is:
2010-05-15 22:37:20.797 PageControl[37962:207] * -[MyDetailController paste:]: unrecognized selector sent to instance 0x1973d10
2010-05-15 22:37:20.797 PageControl[37962:207] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[MyDetailController paste:]: unrecognized selector sent to instance 0x1973d10'
Note: I have access to the textfield tag (if that helps?)
I'm a little past the beginner stage in objective-c, but still not great. My code is currently below, and at https://gist.github.com/d634329e5ddf52945989
Thanks all.
MyDetailController.h
#interface MyDetailController : UITableViewController <UITextFieldDelegate,UINavigationControllerDelegate>
{
//...(lots in here)
}
#end
#interface UIResponder(UIResponderInsertTextAdditions)
- (void) insertText: (NSString*) text;
#end
MyDetailController.m
#implementation MyDetailController
//.... (lots in here)
- (void)addDecimal:(NSNotification *)notification {
// Apend the Decimal to the TextField.
//savedAmount.text = [savedAmount.text stringByAppendingString:#"."];
NSLog(#"Decimal Pressed");
NSLog(#"tagClicked: %d",tagClicked);
switch (tagClicked) {
case 7:
//savedAmount.text = [savedAmount.text stringByAppendingString:#"."];
break;
case 8:
//goalAmount.text = [goalAmount.text stringByAppendingString:#"."];
break;
case 9:
//incrementAmount.text = [incrementAmount.text stringByAppendingString:#"."];
break;
case 10:
//incrementAmount.text = [incrementAmount.text stringByAppendingString:#"."];
break;
}
[self insertText:#"."];
}
-(void)textFieldDidBeginEditing:(UITextField *)textfield{
//UITextField *theCell = (UITextField *)sender;
tagClicked = textfield.tag;
NSLog(#"textfield changed. tagClicked: %d",tagClicked);
}
#end
#implementation UIResponder(UIResponderInsertTextAdditions)
- (void) insertText: (NSString*) text
{
// Get a refererence to the system pasteboard because that's
// the only one #selector(paste:) will use.
UIPasteboard* generalPasteboard = [UIPasteboard generalPasteboard];
// Save a copy of the system pasteboard's items
// so we can restore them later.
NSArray* items = [generalPasteboard.items copy];
// Set the contents of the system pasteboard
// to the text we wish to insert.
generalPasteboard.string = text;
// Tell this responder to paste the contents of the
// system pasteboard at the current cursor location.
[self paste: self];
// Restore the system pasteboard to its original items.
generalPasteboard.items = items;
// Free the items array we copied earlier.
[items release];
}
#end
UIViewController is a UIResponder... but it's not the UIResopnder that should receive the insertText: message. You want to call insertText: on the UITextField itself. If you take a look at UIResponder.h you'll see the following comment near the paste: method
// these methods are not implemented in NSObject
Meaning that it's not necessarily safe to call them on just any UIResponder. They can only safely be called on subclasses such as UITextField and UITextView which actually implement them. This was a really strange design decision on Apple's part.

NSString function

I get a null return when i try out my NSString function.
//Track.m
static NSString* trackUrl;
//static NSString* getTrackNumberUrl;
#implementation Track
- (NSString*)trackUrl {
return #"http://site.com/?a=";
}
- (NSString*)setTrackNumberUrl:(NSString*)trackNumberUrl {
if (trackUrl != trackNumberUrl) {
return [trackUrl stringByAppendingFormat:trackNumberUrl];
}
return #"Error no trackNumber";
}
- (NSString*)getTrackNumberUrl:(NSString*)trackNumber {
return [[[self alloc] setTrackNumberUrl:trackNumber] autorelease];
}
#end
MainView.m, just to show the return answer in NSlog
- (NSString *) trackNumber{
return [track getTrackNumberUrl:#"86147224549XX"];
}
- (void)drawRect:(CGRect)rect {
NSLog(trackNumber);
}
I get a null return answer? Have i miss something? Thanks.
Edit some in Track.m
- (NSString*)setTrackNumberUrl:(NSString*)trackNumberUrl {
if (trackUrl != trackNumberUrl) {
return [trackUrl stringByAppendingString:trackNumberUrl];
}
return #"Error no trackNumber";
}
- (NSString*)getTrackNumberUrl:(NSString*)trackNumber {
return [[[Track alloc] setTrackNumberUrl:trackNumber] init];
}
This is how it should work.
getTrackNumberUrl --> setTrackNumberUrl --> trackUrl (return) --> setTrackNumberUrl + trackNumber --> getTrackNumberUrl (trackNumberUrl = trackUrl + trackNumber)
I have this code to set reference to Track
#class Track;
#interface MainView : UIView {
Track *track;
}
#property (nonatomic, retain) IBOutlet Track *track;
Well if don't should use self alloc, what should i use?
You have a lot of problems with your code.
return [trackUrl stringByAppendingFormat:trackNumberUrl];
You should not use an arbitrary string as a format, because if it contains a format specifier like "%d" then the method will go looking for a variable that isn't there, and will likely crash. You should use stringByAppendingString: instead. However, that doesn't seem to be what you want here, since the method name is setTrackNumberUrl:. If you want to change the value of the trackUrl variable, you can't call stringByAppendingFormat:; all that does is return a new string and leave the original alone. I think you simply want something like
[trackUrl release];
trackUrl = [trackNumberUrl retain];
Another problem:
return [[[self alloc] setTrackNumberUrl:trackNumber] autorelease];
In this context, self is an instance of Track. An instance won't understand the alloc message, that must be sent to a class. It will return a new instance, to which you should send an init message. So you would do something like [[Track alloc] init].
NSLog(trackNumber);
The first parameter to NSLog is a format string, so for the same reasons as above you shouldn't use a variable, you should do something like this: NSLog(#"%#", trackNumber); That line of code prints the value of the variable, trackNumber. Considering that you have a method named trackNumber just above it, I wonder if what you really want to do is call the method and get the result. In that case, you need to write it as [self trackNumber] which will call the method and return an NSString.
Most probably track is nil in the trackNumber - have you set it to a correct reference to a Track object?
Also, this code
- (NSString*)getTrackNumberUrl:(NSString*)trackNumber {
return [[[self alloc] setTrackNumberUrl:trackNumber] autorelease];
}
is incorrect. Why are you using [self alloc]? You're allocating a new Track object (using a static method on an object reference, not on a class name, which is an error), setting it's track number URL, and returning an autoreleased NSString, but you're leaking the Track object you allocated.
return [trackUrl stringByAppendingFormat:trackNumberUrl];
I'm not sure bout this one,
try using it as a format for string.
return [trackUrl stringByAppendingFormat:#"%#",trackNumberUrl];

Possible View Caching problem?

I'm building an iphone app and I've got a table view with some textfields inside the cells, the content of the fields is set in viewWillAppear (its a grouped TableView w/ 3 fields that are always the same). The content of the text fields is retrieved from getter methods that return values from various class variables.
The problem I'm having is the getter seems to be returning the original value, not the value that is modified by the setter method. The class variable is an NSMutableString. Is it possible the view is caching the method call?
//header file
#implementation ManageWorkoutViewController : UIViewController {
NSMutableString *workoutDifficulty;
}
-(void)setWorkoutDifficulty:(NSString *)value;
-(NSString *)getWorkoutDifficulty;
#end
//implementation file
-(NSString *)getWorkoutDifficulty {
if (nil == workoutDifficulty) {
workoutDifficulty = [NSMutableString stringWithString:#"Easy"];
}
NSLog(#"getter: Returning workoutDifficulty as: %#", workoutDifficulty);
return workoutDifficulty;
} //end getWorkoutDifficulty
-(void)setWorkoutDifficulty:(NSString *)value {
workoutDifficulty = [NSString stringWithFormat:#"%d", value];
NSLog(#"setter: workoutDifficulty set as: %#", workoutDifficulty);
}//end setWorkoutDifficulty
//elsewhere in the implementation another table view is
//pushed onto the nav controller to allow the user to pick
//the difficulty. The initial value comes from the getter
workoutDifficultyController.title = #"Workout Difficulty";
[workoutDifficultyController setOriginalDifficulty:[self getWorkoutDifficulty]];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
[(UINavigationController *)self.parentViewController pushViewController:workoutDifficultyController
animated:YES];
//then in that workoutDifficultyController it calls back into the first controller to set the selected value:
[manageWorkoutController setWorkoutDifficulty:selectedDifficulty];
You've got many issues here. First, you're creating your accessors incorrectly. The problem that's particularly causing you trouble is this line:
workoutDifficulty = [NSString stringWithFormat:#"%d", value];
value is an NSString here. You should be receiving a warning about this. I believe "Typecheck Calls to printf/scanf" is turned on by default, and should catch this. workoutDifficulty is being set to some random number (probably taken from the first 4 bytes of value).
Here is what you probably meant. I would probably switch workoutDifficulty to an enum, but I'm keeping it an NSString for consistency with your code. I'm also doing this without properties because you did, but I would use a property here.
//header file
#implementation ManageWorkoutViewController : UIViewController {
NSString *_workoutDifficulty;
}
-(void)setWorkoutDifficulty:(NSString *)value;
-(NSString *)workoutDifficulty; // NOTE: Name change. "getWorkoutDifficulty" is incorrect.
#end
//implementation file
-(NSString *)workoutDifficulty {
if (nil == workoutDifficulty) {
_workoutDifficulty = [#"Easy" retain];
}
NSLog(#"getter: Returning workoutDifficulty as: %#", _workoutDifficulty);
return _workoutDifficulty;
} //end workoutDifficulty
-(void)setWorkoutDifficulty:(NSString *)value {
[value retain];
[_workoutDifficulty release];
_workoutDifficulty = value;
NSLog(#"setter: workoutDifficulty set as: %#", _workoutDifficulty);
}//end setWorkoutDifficulty
You have to retain workoutDifficulty whenever you set it to a new value (and release the old value).