Question

I am attempting to create a custom subclass of a UIView as follows:

I created a .xib with a UIView that contains a Picker object and Toolbar object, hooked up the Outlets and actions.

enter image description here

CustomPickerView.h

#import <UIKit/UIKit.h>

@interface CustomPickerView : UIView

@property (strong, nonatomic) IBOutlet UIDatePicker* datePicker;
@property (strong, nonatomic) IBOutlet UIBarButtonItem* doneButton;

-(IBAction) buttonDonePush:(id)sender;

@end

CustomPickerView.m

#import "CustomPickerView.h"

@implementation CustomPickerView

-(id) init
{
    self=[[[NSBundle mainBundle] loadNibNamed:@"CustomPickerView" owner:self options:nil] objectAtIndex:0];
    return self;
}

-(void) buttonDonePush:(id)sender
{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"CustomPickerViewDoneButtonPush" object:nil userInfo:[NSDictionary dictionaryWithObject:self.datePicker.date forKey:@"date"]];
}

@end

And finally, in my ViewController I instantiate the object in the viewDidLoad method.

- (void)viewDidLoad
{
    [super viewDidLoad];

    self.customPickerView=[[CustomPickerView alloc] init];
    self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;

    self.dateField.inputView=self.customPickerView;
}

When the user taps on the self.dateField, my CustomPickerView pops up nicely in place of the standard keyboard.

The problem is when the user taps the Done button from my CustomPickerView class, the buttonDonePush action does not fire.

Was it helpful?

Solution

This answer can be considered as the iOS companion to a similar solution I offered recently for iOSX:

Interface-Builder: "combine" NSView-class with .xib

Your arrangement is thus:

  • Mainstoryboard.storyboard
  • MyViewController.h
  • MyViewController.m

  • CustomPickerView.xib

  • CustomPickerView.h
  • CustomPickerView.m

You want to use your customPickerView as a subview of MyViewController.view and want to be able to access it's control widgets from the containing context.

In your example you are creating the customPickerView in code, but another useful scenario is to add it to the storyboard in Interface Builder. This solution will work for both scenarios.

In CustomViewPicker.h

  • declare IBOutlets for your interface elements. You have already done this for your datePicker and doneButton, but you also need an IBOutlet to a UIView which will be the containing view for these items.

    @property (strong, nonatomic) IBOutlet UIView* view;
    

In CustomViewPicker.xib

  • Set the file's owner class to CustomViewPicker in the Identity Inspector.
  • Set the top-level view in the xib to the defaul UIView class (NOT CustomViewPicker).
  • Connect your IBOutlets from the file's owner: view, datePicker, doneButton to their respective IB objects
  • Connect your IBAction from the file's owner: buttonDonePush to the doneButton IB object

In CustomViewPicker.m:

- (id)initWithFrame:(CGRect)frame
{
    //called when initialising in code
    self = [super initWithFrame:frame];
    if (self) {
        [self initialise];
    }
    return self;
}

  - (void)awakeFromNib
  {
      //called when loading from IB/Storyboard
      [self initialise];
  }

- (void) initialise
{
    NSString* nibName = NSStringFromClass([self class]);
    if ([[NSBundle mainBundle] loadNibNamed:nibName
                                      owner:self
                                    options:nil]) {
        [self.view setFrame:[self bounds]];
        [self addSubview:self.view];
    }

}
-(void) buttonDonePush:(id)sender
{
   //button push actions
}

If you want to initialise in code (as you have done), your MyViewController would contain something like this:

- (void)viewDidLoad
{
    [super viewDidLoad];
    CGRect frame = CGRectMake(0, 50, 320, 300);
    self.customPickerView=[[CustomPickerView alloc] initWithFrame:frame];

    self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;
    self.dateField.inputView=self.customPickerView;
}

[edit removed this redundant line: [self.view addSubview:self.customPickerView];]

Alternatively you can create your CustomPickerView - and set it's frame - directly in the storyboard. Just add a custom view to your MyViewController's storyboard scene, and change it's class to CustomPickerView. Link it to your self.customPickerView IBOutlet.

In this case initWithFrame does not get called, but awakeFromNib is invoked when MyViewController loads it's CustomPickerView subview. Your MyViewController's viewDidLoad would then look like this:

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.customPickerView.datePicker.datePickerMode=UIDatePickerModeTime;
    self.dateField.inputView=self.customPickerView;
}

If you want to get your button push action out of the customPickerView, you might consider using a delegate, which could be more self-contained than your use of NSNotification (but that issue reaches beyond your original question).

OTHER TIPS

EDIT:

An answer above pointed this out, but in the init method you are setting self, but this happens before self is ever initialized. If you could show the code where you are creating this specific view, it would help a lot. Here's my suggestion.

In your class that is controlling the deployment of this custom view:

//to invoke your view
CustomPickerView *myView;
NSArray *xibContents = [[NSBundle mainBundle]loadNibNamed:@"CustomPickerView" owner:nil options:nil];
for (id xibObject in xibContents) {
    if ([xibObject isKindOfClass:[CustomPickerView class]]) {
        myView = (CustomPickerView *)xibObject;
        break;
    }
}
//now *myView is instantiated as your custom picker view
//do what you want here, add to subview, set frame, etc

In the CustomPickerView.m file, remove the init method.

PREVIOUS ANSWER:

You are using NSNotificationCenter in this implementation. When a user touches the done button, an NSNotification is posted. You must explicitly "opt in" and "listen" for these notifications. You do that by registering with the notification center.

In viewDidLoad:

 [[NSNotificationCenter defaultCenter] addObserver:self
    selector:@selector(receivedNotification:) 
    name:@"CustomPickerViewDoneButtonPush"
    object:nil];

Then you need to implement the selector you specified up there:

-(void)receivedNotification:(NSNotification *)note {
    NSDictionary *obj = [note object];
    NSLog(@"%@",obj);
    //dismiss the date picker here...
    //etc...
}
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top