I'm trying to learn iOS from an entirely Android background. I would like to build a UIPickerView Util class that can be reused over and over again as a separate class throughout my app and I'm receiving the EXC_BAD_ACCESS message and I'm not sure why. So I have two questions:
I've not seen anything about separating this as a different class, is this because this is an improper way to handle this problem?
What's wrong with this basic (mostly generated) code that would be giving me the EXC_BAD ACCESS message? I've read that this is related to memory issues. I'm using ARC so why is this an issue?
Here are the beginnings of the class that I'm trying to build.
Header file
#import <UIKit/UIKit.h>
#interface PickerTools : UIViewController<UIPickerViewDelegate>
#property (strong, nonatomic)UIPickerView* myPickerView;
-(UIPickerView*)showPicker;
#end
Implementation file
#import "PickerTools.h"
#implementation PickerTools
#synthesize myPickerView;
- (UIPickerView*)showPicker {
myPickerView = [[UIPickerView alloc] initWithFrame:CGRectMake(0, 200, 320, 200)];
myPickerView.delegate = self;
myPickerView.showsSelectionIndicator = YES;
return myPickerView;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow: (NSInteger)row inComponent: (NSInteger)component {
// Handle the selection
}
// tell the picker how many rows are available for a given component
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component {
NSUInteger numRows = 5;
return numRows;
}
// tell the picker how many components it will have
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1;
}
// tell the picker the title for a given component
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSString *title;
title = [#"" stringByAppendingFormat:#"%d",row];
return title;
}
// tell the picker the width of each row for a given component
- (CGFloat)pickerView:(UIPickerView *)pickerView widthForComponent:(NSInteger)component {
int sectionWidth = 300;
return sectionWidth;
}
#end
Here's how I'm calling it from a method in a UITableViewController class:
PickerTools *picker = [[PickerTools alloc]init];
[[self view]addSubview:[picker showPicker]];
What are you doing with showPicker? showPicker is misleading because it doesn't actually show. It only returns a pickerView with a frame. At some point you need to add it to a view using addSubview or use a UIPopoverController or something.
If you are just creating your view controller class within the scope of a method, then it is getting released as soon as that method is done running. Then all bets are off. The picker is trying to access the delegate (which is the view controller) but the view controller is nowhere to be found because it was released.
You'll have to post some usage code. By itself, this code should work, but it's how you're using it.
Also, you don't have to use a UIViewController just to "control" a simple view. Consider making a custom class and just sublcass NSObject.
EDIT:
After looking at your posted code my suspicions were correct. You need to "retain" your PickerTools instance. Meaning, you need to save the "picker" variable (misleading again) as a strong property on the calling view controller. It gets released right after you're adding the pickerview as a subview. The pickerView is alive because it's being retained by it's superview but the object that holds it (the delegate "picker") is dead. Make sense?
Related
I have made the basic build and it includes a picker. So whatever units of conversion the user selects from the picker, the labels should be updated to show the names of the units for that conversion and once the user enters the number in the text box and hits the button, the answer should be displayed in the second text box. Now the code executes, but the problem is no matter what I pick in the picker, it always does the weight conversion. I know I need a switch statement in there, but I'm not sure how or where I need to implement it.
Another problem is that the labels update, but only when the "Convert" button is pressed, so please help me fix this.
This is my ViewController.h file
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
<UIPickerViewDelegate, UIPickerViewDataSource> {
IBOutlet UITextField *field1;
IBOutlet UITextField *field2;
IBOutlet UIPickerView *picker;
IBOutlet UILabel *label1;
IBOutlet UILabel *label2;
}
#property (weak, nonatomic) IBOutlet UILabel *textLabelData1;
#property (weak, nonatomic) IBOutlet UILabel *textLabelData2;
#property (nonatomic, retain) UIPickerView *picker;
-(IBAction) currencyConvert:(id)sender;
-(IBAction) weightConvert:(id)sender;
-(IBAction) distanceConvert:(id)sender;
#end
This is my ViewController.m file
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
#synthesize picker;
static NSString *pd[3] = {#"Currency", #"Weight", #"Speed"};
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark UIPickerViewDelegate & UIPickerViewDataSource methods
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component
{
return 3;
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:
(NSInteger)row forComponent:(NSInteger)component;
{
return pd[row];
}
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:
(NSInteger)row inComponent:(NSInteger)component
{
NSLog(#"didSelectRow %li, inComponent: %li", row, component);
}
-(IBAction)currencyConvert:(id)sender
{
if (pd[0])
{
self.textLabelData1.text = #"Dollar";
self.textLabelData2.text = #"Rupees";
float dollar = [[field1 text] floatValue];
float rupees = dollar * 64.15;
[field2 setText: [NSString stringWithFormat: #"%3.3f", rupees]];
}
/* self.textLabelData1.text = #"Dollar";
self.textLabelData2.text = #"Rupees";
float dollar = [[field1 text] floatValue];
float rupees = dollar * 64.15;
[field2 setText: [NSString stringWithFormat: #"%3.3f", rupees]]; */
}
-(IBAction)weightConvert:(id)sender
{
if (pd[1])
{
self.textLabelData1.text = #"Kilogram";
self.textLabelData2.text = #"Lbs";
float kilogram = [[field1 text] floatValue];
float pounds = kilogram * 2.20462;
[field2 setText: [NSString stringWithFormat: #"%3.3f", pounds]];
}
}
-(IBAction)distanceConvert:(id)sender
{
if (pd[2])
{
self.textLabelData1.text = #"Mile";
self.textLabelData2.text = #"Kilometer";
float miles = [[field1 text] floatValue];
float kilometers = miles * 1.60934;
[field2 setText: [NSString stringWithFormat: #"%3.3f", kilometers]];
}
}
/* Pseudocode for switch statement
-- I tried doing this here, but it gives me an
error saying that "row" is undefined. I understand that
it is a local variable, but how else would I write the
switch statement?
Also, is the placement of the switch statement appropriate here
or will I need to implement it in a function?
switch (row)
{
case 1:
call currencyConvert
case2:
call weightConvert
case3:
call distanceConvert
}
*/
The link to the picture of my Main.storyboard
This isn't the ideal UI and creates inefficiencies, but its your app. I just wanted to point out that you might want to spend time working on its design and structure.
As for a solution to your problem, I cant fix everything for you, but I'll point out some things to make it easier for you.
The reason your labels are not updating is because you are not updating anything until you hit the convert button. The below function is where you'll get the event for the picker's new row
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:
(NSInteger)row inComponent:(NSInteger)component
{
NSLog(#"didSelectRow %li, inComponent: %li", row, component);
}
Try this instead:
-(void)pickerView:(UIPickerView *)pickerView didSelectRow:
(NSInteger)row inComponent:(NSInteger)component
{
NSLog(#"didSelectRow %li, inComponent: %li", row, component);
switch (row) {
case 0:
self.textLabelData1.text = #"Dollar";
self.textLabelData2.text = #"Rupees";
break;
case 1:
// and so on
}
}
Your next issue is that you're not telling your "convert" functions what row is currently selected, and your conditional if does nothing.
In obj-c conditional statements used directly on a variable are basically just checks for initialization. if(x) simply means has x been initialized as a variable = true, if not = false. That's probably not the logic you're looking for in this case.
Next, It appears you're trying to link 3 different functions to the same button press? This is where your above if related issue is causing problems, all three functions are firing at the same time... but only one gets the last say. Presumably the one you linked last, though i cant say for sure. I'd suggest a single function that handles the button press and then relays to the correct methods.
Last, you're never telling the functions anything about the state of the picker. You need to set it as an IBOutlet so your variables will have access to it. Then you can use the picker's selected row function to determine which conversion to use.
The things I highlighted will fix your problems, BUT I would highly recommend checking out some tutorials so you'll learn more about why things do what. I'd recommend: https://www.raywenderlich.com/
Hope that helps!
Currently working on an app for spotting trains using CoreData. Basically, each train is stored with a corresponding set of sightings. When a user sees a train they log a sighting and that sighting is tied back to a train serial number (just basic data relationships)
I'm trying to populate a UIPickerView with serial numbers however I am running into some difficulty. I'm planning on using this specific PickerView multiple times, so it has its own class and is not implemented in the ViewController.
I have set the delegate and dataSource correctly, but the PickerView is never populated. From the NSLog and printf code that is in each function I can tell that titleForRow is never called, and neither is numberOfRowsInComponent Here is my code for the UIPickerView class:
-(id)init
{
//init super class.
self = [super init];
//get allocate array reader (TrainSightingReaderWriter) then set the array that this picker will be getting data from.
[self setArrayReader:[[TrainSightingReaderWriter alloc] init]];
[self setArray: [[self arrayReader] getTrainSerialArray]];
NSLog(#"INIT PickerView");
//return this object.
return self;
}
-(int)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
printf("At component counter\n");
return 1;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
printf("At counter. %d is output\n", _array.count);
return _array.count;
}
-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
NSLog(#"Returning a value.");
return [self array][row];
}
ViewController code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"We have made it to the AddSightVC.");
TrainPicker * mypicker;
mypicker = [[TrainPicker alloc]init];
//ISSUES
_trainPickerScroller.delegate = mypicker;
_trainPickerScroller.dataSource = mypicker;
[_trainPickerScroller reloadAllComponents];
[_trainPickerScroller reloadInputViews];
// Do any additional setup after loading the view.
}
TrainPicker needs to be an instance variable for the view controller so that it is not garbage collected (either in the .h as a property or in an interface section in the .m file). Creating a local variable in viewDidLoad for it will cause it to be garbage collected if you are using ARC.
I have a UIPickerView that I am trying to set the datasource for; once the datasource is set, I place it into a modal popover to be displayed. Here is the code - manicuristArray is defined as NSArray, pvManicurist is the UIPickerView, all of the delegates for UIPickerView have been set correctly, as per samples I have found on SO):
-(void) showModalManicurist:(int)tag {
UIViewController* popoverContent = [[UIViewController alloc] init];
UIView *popoverView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 300, 216)];
popoverView.backgroundColor = [UIColor redColor];
popoverContent.contentSizeForViewInPopover = CGSizeMake(300.0, 216.0);
// define the UIPickerView
pvManicurist.frame = CGRectMake(0, 0, 300, 216);
// fill the names from the d/b into manicuristArray
PreferenceData *pv = [PreferenceData MR_findFirst]; // (everything is in one record)
NSLog(#"pv.aStaffPos1: %#", pv.aStaffPos1);
if(pv) { // fill the UIPickerView
self.manicuristArray = [[NSArray alloc] initWithObjects: pv.aStaffPos1, pv.aStaffPos2, pv.aStaffPos3, pv.aStaffPos4, pv.aStaffPos5,
pv.aStaffPos6, nil];
NSLog(#"\nmanicuristArray.count: %d",manicuristArray.count);
pvManicurist.dataSource = self.manicuristArray; [pvManicurist reloadAllComponents];
}
// add it to the popover
[popoverView addSubview:pvManicurist];
popoverContent.view = popoverView;
popoverController = [[UIPopoverController alloc] initWithContentViewController:popoverContent];
popoverController.delegate = (id)self;
[popoverController setPopoverContentSize:CGSizeMake(300, 216) animated:NO];
// show it below the staff name textbox
[popoverController presentPopoverFromRect:boStaff.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionUp
animated:YES];
}
The problem is I am getting this build warning:
Build error: Assigning to 'id' from incompatible type 'NSArray *'
which I believe is causing the UIPicker view not to be put into the UIPopover. I have several other popovers, all with UIDatePickers in them, and they work fine. I have looked on SO and Google and found nothing that answers this particular question, which is: why is this not working? and how do I fix the build error?
UPDATE: here are the delegate methods for UIPickerView:
//-- sets number of columns
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pvManicurist {
return 1; // One column
}
//-- sets count of manicuristArray
-(NSInteger)pickerView:(UIPickerView *)pvManicurist numberOfRowsInComponent:(NSInteger)component {
return manicuristArray.count; //set number of rows
}
//-- sets the component with the values of the manicuristArray
-(NSString *)pickerView:(UIPickerView *)pvManicurist titleForRow:(NSInteger)row forComponent:(NSInteger)component {
return [manicuristArray objectAtIndex:row]; //set item per row
}
And here is the interface from the .h file:
#interface AppointmentsViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UINavigationControllerDelegate, UIActionSheetDelegate, UITextFieldDelegate,UIPickerViewDelegate, UIPickerViewDataSource > {
You cannot assign an array as a data source for a data picker, because arrays do not provide the information the picker needs. In order to work correctly, the picker needs answers to at least these three questions:
How many components a picker should have,
How many rows each component has, and
What data to put in each row of each component.
The data source answers the first two questions by implementing the data source protocol: you need
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
to return the number of components, and
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
to return the number of rows in a given component. Implement both methods in your class, and then assign
pvManicurist.dataSource = self;
Of course you need to implement methods of the delegate as well, but since you assign popoverController.delegate = (id)self; chances are that you have done that already.
The problem is with the assignment
pvManicurist.dataSource = self.manicuristArray;
The dataSource has to be an object conforming to the protocol UIPickerViewDataSource, i.e. an object of type id<UIPickerViewDataSource. Clearly NSArray doesn't conform to the protocol.
You need to assign a controller conforming to that protocol, not the data itself.
Your receive that error message because you are trying to set an NSArray* as the datasource of your picker view. The datasource of your picker view should be an instance of a class that conforms to the UIPickerViewDataSource protocol.
For further information on this protocol visit UIPickerViewDataSource Protocol Reference of the iOS documentation.
Basically I would like to have the user click in the text field and it bring up a populated pickerview rather than a keyboard. This would also need a toolbar with a done button as well Im presuming. I currently have the field set as an output and action and not much more. I also have an actionsheet in code being used for when the user submits something as well if that makes any difference to possibly using an actionsheet for this as well.
I tried researching this topic but 99% of the topics were with datepickers rather than pickerviews (or very old code).
Here is an image of what it looks like for reference.
UITextField now has an inputView property. Here you can assign it a display that you want including a UIPickerView. You must setup the pickerview though and must implement UITextFieldDelegate and UIPickerViewDataSource in your .h:
#interface ViewController : UIViewController <UIPickerViewDataSource, UIPickerViewDelegate>
Then create the picker view and assign it to the textfield.
UIPickerView *pickerView = [[UIPickerView alloc] init];
pickerView.dataSource = self;
pickerView.delegate = self;
// ... ...
self.pickerTextField.inputView = pickerView;
Because you implemented the UIPickerView interfaces you must now implement these methods:
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component;
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView;
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component;
You should then be good to go. Check out the documentation for some other methods if you need more information.
New to iOS and trying my best to get to grips with memory management and Instruments.
My problem is I'm getting weird memory losses when adding/removing a pickerview. I've set up a very basic example consisting of two buttons: one creates and shows the picker. The other one removes it from the main view.
Analyzer comes up clean and Instruments doesn't report any obvious leaks. BUT - heap memory continues to grow when I repeat this "show and hide" operation.
Here's the code. I'm on XCode 4.5, ARC enabled:
My ViewController.h:
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UIPickerViewDataSource,UIPickerViewDelegate>
#property (nonatomic,strong) UIPickerView *myPickerView;
- (IBAction)pickerButtonPressed:(id)sender;
- (IBAction)freeButtonPressed:(id)sender;
#end
ViewController.m:
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)pickerButtonPressed:(id)sender {
NSLog(#"Tap");
// create picker view on first button tap
UIPickerView* newPicker = [[UIPickerView alloc] initWithFrame:CGRectMake(20, 20,160, 160)];
newPicker.dataSource = self;
newPicker.delegate = self;
newPicker.showsSelectionIndicator = YES;
self.myPickerView = newPicker;
[self.view addSubview:newPicker];
}
- (IBAction)freeButtonPressed:(id)sender {
NSLog(#"Free");
// remove from view and release
[self.myPickerView removeFromSuperview];
self.myPickerView = nil;
}
-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
return 5;
}
-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
return 2;
}
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
return #"Meh";
}
#end
Objects List on Instruments shows UIPickerView come and go nicely with every iteration, so it looks like it's being deallocated correctly. Well, not quite. This is a sample from the heapshot:
(sorry I'm not allowed to post images yet)
Now, if I'm actually releasing the pickerview correctly (removing from view and setting its reference to nil) where do those allocations come from?
On a side note - that UIPickerTableViewTitledCell thing on the stack trace strikes me as suspect because some people have had similar problems with leaking pickerviews involving that (see this one and this one), but I can't make head or tails of what I'm supposed to do with this. Not sure if they're actually related to my issue but with any luck they might point in the right direction.
Any tips?