I am trying to create the UIPickerview as described in the iPhone development book by Dave Mark. I have a NSArray which is declared as a property in the h file which will store the data for the UIPickerview. So here is what I have:
in the .h file:
#interface RootViewController : UIViewController {
NSArray *dateForPicker;
}
#property (nonatomic, retain) NSArray *dateforPicker;
#end
In the .m file viewDidLoad method (I do have #synthesize for thedateForPicker property at the beginning of the .m file):
NSArray *tempArray = [[NSArray alloc] initWithObjects:#"1",#"2",#"3",#"4",#"5", nil];
self.dateforPicker = tempArray;
[tempArray release];
When the UIPickerview comes up, it comes up with "?" in all the rows. So when I used a breakpoint to inspect the values of tempArray and dateForPicker in the viewDidLoad method, I find that the tempArray is fine but the dateForPicker never gets the values from the tempArray. Xcode says "Invalid Summary" for the dateForPicker array and has "out of scope" as the values for the five rows. What is going on? As described by the book, this should work.
Here is the code for the UIPickerView:
#pragma mark -
#pragma mark picker data source methods
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return [dateforPicker count];
}
#pragma mark picker delegate methods
-(NSString *)pickView:(UIPickerView *)pickerView titleForRow:(NSInteger)row
forComponent:(NSInteger)component{
return [dateforPicker objectAtIndex:row];
}
#end
Some problems with your code. I'm not clear if you've typed this into the question manually or copied and pasted from your actual code:
You are setting self.dateforPicker and not self.dateForPicker, there is a difference in capitalisation between your ivar and your property. In iOS the compiler will have synthesized a dateforPicker ivar when you declared your property, which was set in your viewDidLoad, but in your other methods you may be referring to the dateForPicker ivar, which is never touched.
Your RootViewController does not declare that it implements the UIPickerViewDataSource or UIPickerViewDelegate protocols
Your declaration of the titleForRow method is wrong - yours begins with pickView rather than pickerView so will not get called.
If you have the correct number of rows in your component (you said multiple question marks, how many?), so it looks like the data source is wired up properly, but you also need to connect the delegate, as this is what actually supplies the values for each row. The datasource, confusingly, only supplies the number of components and the number of rows per component.
Related
I am doing a tuturial on Lynda.com for objective-c, and ran accross this example code. This is a part of the ViewController.m file. The idea behind the exercise was to create a picker object with custom elements in it.
The following code works just fine and gives me a picker with "happy" and "sad" as the options:
#implementation ViewController
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return [[self moods] count];
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component{
return self.moods[row];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
self.moods = #[#"happy",#"sad"];
}
However, I prefer square brackets to dot syntax and, as you can see I experimented in a few different places with it. Thereturn [[self moods] count was written as return [self.moods count] in the tutorial, but I wanted to use square brackets to verify that it still worked and I understood what was going on, so I changed it and it worked just fine. HOWEVER, I have been trying to do the same thing with the self.moods = #[#"happy",#"sad"]; because I don't like how it looks. I tried:
[[self moods] initWithObjects: #"happy",#"sad", nil];
But I just got a blank picker and a warning "expression result unused". I tried putting _moods = before that expression, and still got a blank picker. What is wrong here?
The reason that [[self moods] initWithObjects: #"happy",#"sad", nil]; is not doing what you expect is due to a misunderstanding in what is happening with regards to dot syntax and how it relates to message sending using square brackets.
Dot syntax is the "syntactic sugar" and recommended way of accessing properties of classes, such as the mood property from your question. Dot syntax is simply a shorthand for accessors (setters / getters) in Objective-C. A quick example might help clear this up.
When dot syntax finds itself on the right hand side of an assignment operator OR as the receiver of a message, the getter method is invoked.
// These two are equivalent
NSArray *allMoods = self.moods
NSArray *allMoods = [self moods]
// These two are equivalent
NSUInteger count = [self.moods count];
NSUInteger count = [[self moods] count];
When dot syntax finds itself on the left hand side of an assignment operator, the setter method is invoked.
// These two are equivalent
self.moods = #[#"happy", #"sad"];
[self setMoods:#[#"happy", #"sad"];
Using dot syntax is not only a nice shorthand, it makes your intentions clearer and newcomers to your code immediately aware that moods is a property of your class.
Also, the reason that [[self moods] initWithObjects: #"happy",#"sad", nil]; is not valid is because -initWithObjects: is an initializer of NSArray that should be called immediately following +alloc. In the piece of code above, [self moods] is returning an NSArray that already exists or lazily instantiating one. For completeness, -initWithObjects should be used as follows:
NSArray *myArray = [[NSArray alloc] initWithObjects:#"happy", #"sad", nil];
I assume you declared #property (strong, nonatomic) NSArray *moods; in the interface since self.moods works.
Setter and getter methods setMoods and getMoods are created automatically.
Here's how the dot syntax boils down to
// These are equivalent
self.moods = #[#"happy",#"sad"];
[self setMoods:#[#"happy",#"sad"]]; // Literal declaration
[self setMoods:[[NSArray alloc] initWithObjects:#"happy",#"sad",nil]]; // Full declaration
This works because you were using the "literal" way of declaring an NSArray* which includes both "allocation" and "initialization".
- (instancetype)initWithObjects: is an instance method which should be called on an instance variable already allocated with alloc. You tried to initialize a variable which has never been allocated in memory.
An slightly cleaner alternative would be:
[self setMoods:[NSArray arrayWithObjects:#"happy",#"sad",nil]];
arrayWithObjects: include both allocation and initialization.
the [self moods] way of referencing it can only be used on the right hand side of an expression, it's calling the getter for the property. self.moods = ... is actually syntactic sugar for [self setMoods:...]
so try [self setMoods:#[#"happy",#"sad"]]
You'll want to read up on the #property declaration and how it "synthesizes" getter and setter methods. What you want to do is "set" the moods property:
[self setMoods: #[#"happy",#"sad"]];
I am new to iOS dev,here is my first app-calculator,
But the NSMuteableArray "_numberArrayWaitingForCalculate" always be "nil",I don't know what to do???
Here is the interface
#interface demoViewController ()
#property (strong,nonatomic)NSString *valueString;
#property (strong,nonatomic)NSMutableArray *numberArrayWaitingForCalculate;
#end
here is the implement 1
#implementation demoViewController
#synthesize numberArrayWaitingForCalculate=_numberArrayWaitingForCalculate;
- (NSMutableArray *)numberWaitingForCalculate
{
if(!_numberArrayWaitingForCalculate)
_numberArrayWaitingForCalculate=[[NSMutableArray alloc]init];
return _numberArrayWaitingForCalculate;
}
here is the tapNumber method
- (IBAction)tapNumber:(UIButton *)numberButton {
if(LastButtonWasMode)
{
_valueString=#"";
LastButtonWasMode=NO;
}
NSString *numberAsString = numberButton.currentTitle;
_valueString=[_valueString stringByAppendingString:numberAsString];
result.text=[NSString stringWithFormat:#"%#",_valueString];
}
here is tapPlus method
- (IBAction)tapPlus:(id)sender {
[_numberArrayWaitingForCalculate addObject:[NSNumber numberWithInt:[_valueString intValue]]];
resultOfAllNumberInputBefore +=[_valueString intValue];
[self setMode:1];
}
The following line should be using the property and not the instance variable. i.e. you're not actually calling the getter that allocates the array.
Change this line:
[_numberArrayWaitingForCalculate addObject:[NSNumber numberWithInt:[_valueString intValue]]];
to:
[self.numberArrayWaitingForCalculate addObject:[NSNumber numberWithInt:[_valueString intValue]]];
You created a getter that "lazy loads" the mutable array (meaning that you create it if it doesn't exist already. That's a valid approach.
However, if you do that, you need to ALWAYS use the getter. You're using the iVar directly (_numberArrayWaitingForCalculate). Don't do that. Replace all instances of "_numberArrayWaitingForCalculate" with [self numberArrayWaitingForCalculate] except in the implementation of your getters/setters and probably your dealloc method.
So your tapPlus method should read:
- (IBAction)tapPlus:(id)sender
{
[[self numberArrayWaitingForCalculate] addObject:[NSNumber numberWithInt:[_valueString intValue]]];
resultOfAllNumberInputBefore +=[_valueString intValue];
[self setMode:1];
}
EDIT:
By the way, for something as lightweight as an empty mutable array, I think I would take a different approach. Rather than lazy-loading the array in a getter, I would create an init method for my class that created an empty mutable array and installed it in the iVar.
Objects like view controllers can be initialized more than one way. It might get initialized with initWithNibName:bundle: or with initWithCoder:
What I do in that case is to create a method doInitSetup, and call it from both places.
I am trying to implement the XYPieCharts but am currently unable to get it to display. While debugging I placed some log statements in the data source methods but none of these are being fired.
I have included the delegate (not being used) and datasource as such
#interface DwinklySalaryViewController : UIViewController <UITextFieldDelegate, XYPieChartDelegate, XYPieChartDataSource>
#property (nonatomic, strong) IBOutlet XYPieChart *salaryPieChart;
I have also checked that I have linked up this IBOutlet to the XYPieChart element in the Interface Builder. There is no delegate/datasource outlet in the Interface Builder so I have added that programatically as in the demo. This, and the rest of the options are set in the viewDidLoad method of the a view controller that contains the XYPieChart view.
[_salaryPieChart setDelegate:self];
[_salaryPieChart setDataSource:self];
I have then implemented the required methods for the datasource protocol:
- (NSUInteger)numberOfSlicesInPieChart:(XYPieChart *)pieChart
{
NSLog(#"Number of slices: %d", pieChartSlices.count);
return pieChartSlices.count;
}
- (CGFloat)pieChart:(XYPieChart *)pieChart valueForSliceAtIndex:(NSUInteger)index
{
NSLog(#"Value slice: %d", [[pieChartSlices objectAtIndex:index] intValue]);
return [[pieChartSlices objectAtIndex:index] intValue];
}
- (UIColor *)pieChart:(XYPieChart *)pieChart colorForSliceAtIndex:(NSUInteger)index
{
NSLog(#"Returning %#", [sliceColors objectAtIndex:(index % sliceColors.count)]);
return [sliceColors objectAtIndex:(index % sliceColors.count)];
}
Could anyone tell me what I've missed out that causes the data source methods not to trigger?
It appears that with the way XYPieChart works, you have you call reloadData on it at startup. It works ok in either viewDidLoad or viewDidAppear.
for my table view I have the following going on (paraphrased)
.h
#interface OptionsView : UIView <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, retain) NSArray *dataSource;
.m
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self) {
...
self.dataSource = [NSArray arrayWithObjects:options, sections, sponsor, nil];
}
return self;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(self.dataSource) {
NSArray *ds = [self.dataSource objectAtIndex:indexPath.section];
NSDictionary *d = [ds objectAtIndex:indexPath.row];
ActionBlock a = [d objectForKey:ACTION]; // <-- Thread 1: EXC_BAD_ACCESS (code=2, address=0x802)
if(a != nil) a();
}
}
You can see that in my didSelectRowAtIndexPath I'm getting an EXC_BAD_ACCESS but I'm not sure why. Because I'm using arc it isn't a zombie problem (already checked).
Using breakpoints I see that the self.dataSource exists after it's initialized, but not later when it's needed. It doesn't show up as <nil> either, it's just white space. Also, this works in debug but not in release so what does that mean?
** EDIT adding a screenshot **
You will notice that neither ds or d show up.. odd?
So there's two places I can see that might cause the problem. First, what is ACTION? Is it some sort of NSString? Just want to make sure that you're using a valid key object. Second, (and more likely the problem), it looks like ActionBlock is some kind of code block you're storing in a collection array. Are you copying that block before you store it in the array/dictionary? You must copy any block you intend on keeping around (storing) longer than the scope it was created in. This is easy to do. For example:
void (^NewBlock)(void) = [^{
....code....
} copy];
Alternately:
void (^NewBlock)(void) = ^{
....code....
};
[dictionary setObject:[NewBlock copy] forKey:ACTION]; //Assuming #define ACTION #"action"
This copies it onto the heap so that it can be stored. If you don't copy it, you'll get BAD_EXC_ACCESS anytime you try to reference the block outside the scope it was created in.
It seems you´re using a UIViewController instead of a UITableViewController. If I remember correctly, you have to go to the inspector and drag the delegate and datasource from the tableView to the UIViewController. I don´t remember exactly and I´m not on my computer but I´m sure I did it several times before I began to use a UITableViewController for every tableView I have.
Oh, and i wouldn´t use dataSource as a name for your array. Just to prevent naming conflicts.
I'm building an application that inserts records into a database, and it pulls items from that database from lookup tables so it has the proper items in predefined fields. Obviously a picker is the best route for this method, but I'm running into a problem. There are 2 different pages with about 15 different pickers between the two of them. Now what I need to do is load the picker as it's popped up when the user selects the field.
All the online tutorials and examples I've found are using either the GUI editor to link a picker with a data source, or using one single data source for a picker in a separate file(which I'm not doing).
How do I go about loading an NSMutableArray into a picker when it's called?
Thanks!
Have a data source and delegate class that implements all the required data source/delegate methods, that is defined as conforming to UIPickerViewDataSource and UIPickerViewDelegate and that has #property (retain, nonatomic) NSMutableArray *dataArray; defined in the header. Make the data source/delegate class's methods (such as - (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component) load the strings etc from dataArray. Then when creating/changing the picker, do the following:
MyPickerSource *source = [[MyPickerSource alloc] init];
source.dataArray = [NSMutableArray arrayWithArray:<PreloadedArray>];
[picker setDelegate:source];
[picker setDataSource:source];
[picker reloadAllComponents]; // If re-using an old picker
[source release];
[self addSubview:picker]; // If creating a new picker
Hope this is all clear!
You want to use a UIPickerViewDataSource and UIPickerViewDelegate, and implement the necessary functions, namely numberOfRowsInComponent: and titleForRow:, through these methods you can draw the data from your NSMutableArray.
The important thing to consider is for you to call reloadAllComponents on the pickerview when you need it to update. You can also use selectRow: inComponent: animated:NO to keep the picker scrolled to the appropriate selection when you update the components in the list.