0

Being new to objective-C coding I started out writing a basic app, fully programmatically (not using storyboards or xib) in one file, my AppViewController h and m files. Everything worked lovely.

So then I wanted to break up the mass of code by subclassing sections, and everything went well apart from the UIPickerView. In fact simply commenting out the [background addSubview:colorPicker]; seemed to totally fix the issue. I never found the answer online so I proceeded to make a new document to replicate said issue. So here goes:

UIPickerViewController.h


#import <UIKit/UIKit.h>
#import "Picker.h"
@interface UIPickerViewController : UIViewController
@end

Simply imports my new class.

UIPickerViewController.m

#import "UIPickerViewController.h"
@interface UIPickerViewController ()
@end

@implementation UIPickerViewController
- (void)viewDidLoad
{
    [super viewDidLoad];
    UIView *superview = self.view;
    int height = superview.bounds.size.height;
    int width = superview.bounds.size.width;
    CGRect popupRect = CGRectMake(0, 0, width, height);
    UIView *popup = [[UIView alloc]initWithFrame:popupRect];
    popup.tag = 8;
    [superview addSubview:popup];
    Picker *picker = [[Picker alloc]initWithFrame:popupRect];
    [picker viewAddTypeScreenToView:superview];
}
- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
} 
@end

Sets up a new view with a tag (so that i could reference it later with my new class) Then actions a method from my new class to populate my new view.

Picker.h


#import <UIKit/UIKit.h>

@interface Picker : UIView
<UIPickerViewDataSource,UIPickerViewDelegate>
{
    UIPickerView *colorPicker;
    NSMutableArray *colorsArray;
}

@property (nonatomic, retain) UIPickerView *colorPicker;
@property (nonatomic, retain) NSMutableArray *colorsArray;
@property (strong,nonatomic) UILabel *myValue;

-(void)viewAddTypeScreenToView:(UIView*)superview;

@end

Setting up my variables and accessible method.

Picker.m


#import "Picker.h"

@implementation Picker

@synthesize colorsArray;
@synthesize colorPicker;

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
    }
    return self;
}


-(void)viewAddTypeScreenToView:(UIView*)superview
{
    UIView *baseView =[superview viewWithTag:8];

    int height = baseView.bounds.size.height;
    int width = baseView.bounds.size.width;

    CGRect fullScreen = CGRectMake(0, 0, width, height);
    UIView *background = [[UIView alloc]initWithFrame:fullScreen];
    background.backgroundColor = [UIColor blackColor];

    colorsArray = [[NSMutableArray alloc] initWithObjects:@"Red",@"Blue",@"Yellow",@"Green",nil];

    CGRect myPickerRect = CGRectMake(10, 70, (width/2)-40, 200);
    colorPicker = [[UIPickerView alloc]initWithFrame:myPickerRect];
    colorPicker.dataSource = self;
    colorPicker.delegate = self;
    colorPicker.showsSelectionIndicator = YES;
    [colorPicker selectRow:2 inComponent:0 animated:YES];

    CGRect labelFrame = CGRectMake(10, 10, 180, 50);
    _myValue = [[UILabel alloc]initWithFrame:labelFrame];
    _myValue.textColor = [UIColor redColor];
    _myValue.text = @"select colour";
    [background addSubview:_myValue];
    [background addSubview:colorPicker];
    [baseView addSubview:background];

}



-(NSInteger)numberOfComponentsInPickerView:(UIPickerView *)pickerView{
    return 1;
}

-(NSInteger)pickerView:(UIPickerView *)pickerView numberOfRowsInComponent:(NSInteger)component{
    return colorsArray.count;;
}

-(NSString *)pickerView:(UIPickerView *)pickerView titleForRow:(NSInteger)row forComponent:(NSInteger)component
{
    return colorsArray[row];
}

-(void)pickerView:(UIPickerView *)pickerView didSelectRow:(NSInteger)row inComponent:(NSInteger)component{
    _myValue.text = [NSString stringWithString:colorsArray[row]];
}

@end

And finally the initiation called by the method in the picker class file. This gives me an error along these lines

-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000
2014-03-19 10:29:48.407 Briefcase[1800:60b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UITableViewCellContentView pickerView:titleForRow:forComponent:]: unrecognized selector sent to instance 0x8f2b000'

Which i've read is to do with either the datasource, or ARC systems, however none of the responses that I have found relate to or work with the type of set up that I have above. I'm sure it's something really simple but after a few days of failed searching, it's officially driving me crazy.

Larme
  • 24,190
  • 6
  • 51
  • 81

1 Answers1

0

The problem is most likely that the instance of Picker that is being created in UIPickerViewController is never added to the view hierarchy and thus gets released prematurely (provided we're talking about a project using ARC here).

This leads to the pickerview's delegate and datasource becoming invalid and, basically, pointing at any random object. That's what is causing your crash: A message to your delegate cannot be delivered because the delegate is dead already. The picker still keeps a pointer which used to point at the delegate, but which has become invalid and points at a random object now, in this case a table view cell, which basically doesn't know what to do with this message and crashes.

The problem should go away if you add Picker *picker as an ivar or a retaining / strong property to UIPickerViewController.h - this will retain the picker beyond the scope of the viewDidLoad method and should keep it alive.

But that would be just a workaround, the real problem is your overall design. You said you're new to objective-c and indeed, it looks like you lack a basic understanding of iOS view and view controller hierarchies and, to some degree, the concept of object oriented programming. You might want to dig into something more basic before trying to fix your code because, quite frankly, it should be rather re-written than fixed.

I'd be happy to provide you with suggestions about how to structure your code, but please provide some information about what functionality you'd like to achieve first.

Edit (in response to your comment):

  • As a rule of thumb, do not spread functionality over several classes unless necessary. For objects, which serve a rather infrastructural purpose, like a specialized textfield or a pickerview, always ask yourself: "If I would like to reuse that object in another project, would that be as easy as using any other existing object, like, for example, UILabel?" If the answer is "No", then something is wrong. Ideally, interface objects are self-contained and to use them, you just invoke them, add them to a view and tell them, which text to display or which options to offer. If that information is subject to change or if the object needs to interact with other parts of your code, make use of delegation and protocols. Under no circumstances should the functionality of your object be tied to hard coded values or rely to some view to have a certain tag.

  • If you subclass UIView, the resulting object should behave like any other instance of UIView. It should be added to the view hierarchy by you or some object, but it shouldn't add or remove itself. If it works without being added to the view hierarchy at all, something is wrong. A view serves the purpose of being a part in your interface and all the logic it contains should work to that end, not more, not less.

  • Normally, interface objects should not interfere with one another. If something happens to one object (button pressed, option selected, text changed...) and another object is supposed to reflect that change, it is the view controllers responsibility to make that happen. The view controller is the place where the logic happens. If there is a task which requires a lot of complex logic, it might be a good idea to encapsule that logic into a purpose build class. One such example would be a class which manages network connections. This class should be again self contained: If the view controller needs some remote information, it asks your network class. Once your network class has that information (or failed to retrieve it), it reports back to your view controller. The view controller then updates the interface - under no circumstance should the networking class contain code which affects the interface.

It is important to understand that you could very well ignore these rules and still end up with a working app. And in some cases, the "direct" way may appear to be easier to implement and thus may look very tempting. But you'll pay the price later - once you start debugging your code. If your picker does not behave the way it should, you need to look into several places and wrap your mind around several objects, just to make one interface object behave right. And likely you will break one functionality while fixing the other.

So, try to make it right from the start, even though it requires more planning and learning. Trust me, it pays out, I started out just like you several years ago ;)

Toastor
  • 8,980
  • 4
  • 50
  • 82
  • This indeed fixed the issue! I added Picker *picker as a class variable in UIPickerViewController.h and amended the call in the UIPickerViewController.m file to accommodate for this - and we're back in the game! In my actual project, I have a projectVC which imports uiview classes for a Left,mid and right column and an empty popup view - all added as subviews. The left column has a button to launch a popup method which writes into the previously added popup view (tag 8). If there is a better way around this it would be gladly taken on board. This is how we learn right :) – Nev_Yakamazi Mar 19 '14 at 14:01
  • Right :) See my updated answer for some rules of thumb. And let me know if you need to know more! – Toastor Mar 19 '14 at 14:56
  • Thank you very much for the advice. It's all taken on board. So in this case would I have been better to leave all the code in the viewcontroller class as it was originally, instead of attempting to break the project up into manageable chunks? I have an NSObject class which stores sql data and methods to check database, insert, update, select and delete any generic sql related call. In my entire project I believe this is the only class that follows the advice that you have given :P – Nev_Yakamazi Mar 19 '14 at 15:44
  • If I had a load of buttons that all needed to be laid out in different ways; would it be good practice to create a buttonLayout class with methods for horizontal and vertical placement, that allocates and inits buttons in the correct manner and returns a group of buttons within a view - that can be called and added as a subview within the view controller? Or would that still be bad practice? Being new to it I find it confusing and a pain to have it all in one file and so i'm looking for ways to cut down or break up the code - mainly for my own sanity. Your Advice is hugely appreciated :) – Nev_Yakamazi Mar 19 '14 at 15:55
  • It is perfectly in order for a view to create subviews for its own use, like the button-array you proposed. And manageable chunks are perfect as well, as long as each chunk is a functional unit. Thats the whole point of OOP. Take a networking class as an example - it may well be a single class, but if it becomes too large, it might be a good idea to split it in two classes, one for listening sockets and one for sending sockets. Yet there may still be a single interface class which manages both. Over time, you may define your own best practices which may be different from mine – Toastor Mar 19 '14 at 22:31
  • And you'll probably run against some walls along the way. That's okay. Just be open and self disciplined and try to understand what OOP has to offer and try to harness its strengths and you'll be fine. Oh and by the way - it is best practise to use a three letter prefix for your own classes' names to avoid collisions with existing or yet to come framework class names. So instead of "Picker" you might want to name it "NEVPicker" or "NYKPicker" or something similar :) – Toastor Mar 19 '14 at 22:38