UIPickerView not loading rows - ios

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.

Related

How to fill a pickerview with data and run an activity indicator at the same time?

I have a method to load data from a web service and fill an array, it works fine (let's call it "get_data"). I use this array ("arr") as datasource for a picker view ("pv"), it also works fine, when I run my app, the pickerview is full with items. One day I decided to use an "activity indicator" ("ai"), and now "pv" is always empty, and I have no idea how to fix it. Next is my code:
- (void) viewDidLoad {
[super viewDidLoad];
[ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
[self performSelector:#selector( get_data ) // FILL ARRAY.
withObject:nil
afterDelay:0]; }
//-------------------------------------------------------------------
- (void) get_data { // CALL WEB SERVICE, FILL ARRAY WITH JSON DATA.
[ai stopAnimation]; } // SPINNING STOPS AFTER LONG TASK.
//-------------------------------------------------------------------
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView {
return 1; }
//-------------------------------------------------------------------
- (NSInteger)pickerView :(UIPickerView *)pickerView
numberOfRowsInComponent:(NSInteger)component {
return [arr count]; }
//-------------------------------------------------------------------
- (NSString *)pickerView:(UIPickerView *)pickerView
titleForRow:(NSInteger)row
forComponent:(NSInteger)component {
return [arr objectAtIndex:row]; }
//-------------------------------------------------------------------
- (void) pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger )row
inComponent:(NSInteger )component {
index = [pv selectedRowInComponent:0]; } // INDEX IS A GLOBAL INT.
//-------------------------------------------------------------------
- ( NSAttributedString * ) pickerView:(UIPickerView *)pickerView
attributedTitleForRow:(NSInteger)row
forComponent:(NSInteger)component {
NSString * title = [arr objectAtIndex:row];
NSAttributedString * attString = [[NSAttributedString alloc]
initWithString:title attributes:
#{NSForegroundColorAttributeName:
[UIColor blackColor]}];
return attString; }
I have used "activity indicators" before with "performSelector", and they have worked fine. What I don't understand is why the pickerview methods aren't working anymore with the array, so, my question is: How to fill a pickerview with data and run an activity indicator at the same time?
If I change my viewDidLoad like this, pickerview works, but activity indicator doesn't :
- (void) viewDidLoad {
[super viewDidLoad];
/* [ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
[self performSelector:#selector( get_data ) // FILL ARRAY.
withObject:nil
afterDelay:0]; */
[self get_data];
}
When you use performSelector:, it executes on the same thread as though you had simply called the selector directly.
- (void) viewDidLoad {
[super viewDidLoad];
[ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
[self performSelector:#selector( get_data ) // FILL ARRAY.
withObject:nil
afterDelay:0];
}
- (void) get_data {
// CALL WEB SERVICE, FILL ARRAY WITH JSON DATA.
[ai stopAnimation]; // SPINNING STOPS AFTER LONG TASK.
}
This is identical to:
- (void) viewDidLoad {
[super viewDidLoad];
[ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
[self get_data]; // FILL ARRAY.
}
or simply:
- (void) viewDidLoad {
[super viewDidLoad];
[ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
// CALL WEB SERVICE, FILL ARRAY WITH JSON DATA.
[ai stopAnimation]; // SPINNING STOPS AFTER LONG TASK.
}
If your web service is executing in a different thread or a block and allows you to attach a completion responder, you should put your [ai stopAnimation]; in there.
If this is not the case and you are synchronously calling to a web service for data on the main thread (don't ever perform blocking network operations on the main thread), you could instead shift it all to a background thread:
- (void) viewDidLoad {
[super viewDidLoad];
[ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0L), ^{
// CALL WEB SERVICE, FILL ARRAY WITH JSON DATA.
dispatch_async(dispatch_get_main_queue(), ^{
[ai stopAnimations]; // SPINNING STOPS AFTER LONG TASK.
});
});
Make sure that your picker view's dataSource and delegate are set. After your web service finishes, you need to reload your picker's data source with the updated arr.
- (void) viewDidLoad {
[super viewDidLoad];
pv.dataSource = self;
pv.delegate = self;
[ai startAnimation]; // ACTIVITY INDICATOR STARTS SPINNING.
[self performSelector:#selector( get_data ) // FILL ARRAY.
withObject:nil
afterDelay:0]; }
- (void) get_data {
// CALL WEB SERVICE, FILL ARRAY WITH JSON DATA.
[ai stopAnimation]; // SPINNING STOPS AFTER LONG TASK.
[pv reloadAllComponents];
}

PickerView shows up with no options, even though I have set them

I have a UIPickerView, and it is showing up, but it just shows (null). Why is this? I have it conformed to the delegate's I need in.
EDIT: more detail.
Basically, my app is a flight logbook. You make planes, which you saves, and you can pick it when you make a new session. The planeNum key stores how many planes there are, and the key Plane%liTailNumber has the tail number for that plane.
This is my code:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)thePickerView {
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)thePickerView numberOfRowsInComponent:(NSInteger)component {
return (long)[[NSUserDefaults standardUserDefaults]integerForKey:#"planeNum"];
}
NSMutableArray *tailPickerOptions;
- (NSString *)pickerView:(UIPickerView *)thePickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component {
NSInteger num = [[NSUserDefaults standardUserDefaults]integerForKey:#"planeNum"];
while (num > 0){
--num;
tailPickerOptions = [[NSMutableArray alloc]init];
NSString *dTailNumber = [NSString stringWithFormat:#"%#", [[NSUserDefaults standardUserDefaults]objectForKey:[NSString stringWithFormat:#"Plane%liTailNumber", (long)num]]];
[tailPickerOptions addObject:dTailNumber];
NSLog(#"%#", tailPickerOptions);
}
return tailPickerOptions[row];
}
You initialize a new array for each loop of the while. Your code also doesn't ensure you actually initialize the array - if num = 0, you'll never go in and declare the array. There should be a warning or compiler error for this. In the best case scenario because you keep declaring a new array in your loop, you'll return a max of 1 element ever.
Move your array declaration above the while loop.

Build error: Assigning to 'id<UIPickerViewDataSource>' from incompatible type 'NSArray *'

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.

Building a UIPickerView Util class

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?

Xcode 4.2.1: UIPickerView causing memory leak, using ARC

For one of my last school projects, I am creating an iPad/iPhone application. For some days now I've been working on an issue with a certain memory leak. My application starts out on a specific view-controller (VCMainStatistics_iPad). From there, I push another view-controller (VCSocialMedia_iPad). Afterwards, I go back to the first view-controller.
When I repeat this sequence, I notice (by using Instruments - Activity Monitor) that the memory usage of the app keeps increasing. By disabling parts of the code, I eventually found out it has something to do with the pickerView. This code gives no leaks:
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return 0;
}
However, when I increase the number of rows, leaks start emerging (roughly 0.07 MB per row). Obviously, this is why I believe the pickerView is the cause of the leaks. I've been trying to remove the subviews from the pickerView before deallocation, setting pickerView to nil, and lots of other things... nothing fixes the issue. To hopefully make things a bit clearer, I'll post some more code.
The header file:
#import "UniversalViewController.h"
#define DATATYPE_SOCIALMEDIA 0
#interface VCSocialMedia_iPad : UniversalViewController <UIPickerViewDataSource, UIPickerViewDelegate>
{
NSArray *lMediaTypes;
NSMutableArray *lMediaData;
__weak IBOutlet UIPickerView *pkSocialMedia;
__weak IBOutlet UILabel *lblGraph;
}
#end
PickerView delegate methods:
- (NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
// Get key of requested row
NSString *title = [[lMediaTypes objectAtIndex:row] capitalizedString];
// Capitalize first letter
title = [title capitalizedString];
// Return
return title;
}
- (void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
// Make or clear data lists
if( lGraphDayDataX[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] == nil ){
lGraphDayDataX[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] = [[NSMutableArray alloc] init];
}
else{
[lGraphDayDataX[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] removeAllObjects];
}
if( lGraphDayDataY[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] == nil ){
lGraphDayDataY[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] = [[NSMutableArray alloc] init];
}
else{
[lGraphDayDataY[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] removeAllObjects];
}
// Get required key
NSString *dictKey = [lMediaTypes objectAtIndex:row];
if( [dictKey isEqualToString:#"total_views"] ){
return;
}
// Adjust graph label
lblGraph.text = [NSString stringWithFormat:#"Stats from %#", dictKey];
// Get data of selected row
NSArray *mediaData = [lMediaData objectAtIndex:row];
// Add each day to data lists: inversed order
for( int day = [mediaData count]-1; day >= 0; day-- ){
NSDictionary *dayData = [mediaData objectAtIndex:day];
dictKey = #"wpsd_trends_date";
NSString *date = [dayData objectForKey:dictKey];
// Remove 00:00:00
date = [date stringByReplacingOccurrencesOfString:#" 00:00:00" withString:#""];
[lGraphDayDataX[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] addObject:date];
dictKey = #"wpsd_trends_stats";
NSString *stats = [dayData objectForKey:dictKey];
[lGraphDayDataY[iSelectedServerIndex][DATATYPE_SOCIALMEDIA] addObject:stats];
}
// Update the graphs
[self updateGlobalScreen];
}
PickerView datasource methods:
- (NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView
{
return 1;
}
- (NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component
{
return [lMediaTypes count];
}
Deallocation:
- (void)dealloc
{
pkSocialMedia = nil;
lblGraph = nil;
lMediaData = nil;
lMediaTypes = nil;
}
I only recently converted the project to Objective-C ARC, so there is a good chance this issue has something to do with my lack of experience with the concept. Apart from that, this is also my first Xcode project ever. Hopefully, someone here can help out: please let me know if I need to post more code to clarify things.
Thanks in advance!
Try removing the -(void)dealloc method. It shouldn't be implemented when you're using ARC. If you aren't using ARC, it needs to call [super dealloc].
Never found the solution itself, so I used a workaround: by replacing the NSPickerView with a NSTableView component, the leak did not occur anymore. For everyone who noticed the issue and tried to find a solution: thank you for trying!
I'm having a similar issue. It only happens when the UIPickerView is outside the bounds. The way I fixed it is to never have the UIPickerView move out of bounds (simply fade in and fade out to unhide/hide the UIPickerView). Probably a bug in UIKit.

Resources