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.
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.
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).
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...
}
Related
I am using multiple XIBs/Storyboards to build my application.
I have the views laid out in the XIBs. Some have UIStackView to help organize the layout.
My main storyboard has two views embedded in a UIStackView that pulls in those views that are created with the XIBs.
Nothing will display correctly. The views are mis-sized or do not show up at all, despite displaying properly in interface builder.
My suspicion is the views are being displayed, BEFORE they are fully loaded into the view causing the frames to be different sizes.
I've been told the best practice is to give views their own Storyboard/XIB for better merging, maintenance etc... So that is what I am trying to learn.
Does anyone know the proper way to accomplish what I am doing?
Here is what I am doing:
ViewController
#import "OrangeView.h"
#import "GreenView.h"
#interface ViewController () {
OrangeView *ov;
GreenView *gv;
}
#property (weak, nonatomic) IBOutlet UIView *viewOrange;
#property (weak, nonatomic) IBOutlet UIView *viewGreen;
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//Load Orange View
ov = [[OrangeView alloc] initWithFrame:_viewOrange.frame];
[_viewOrange addSubview:ov];
//Load GreenView
gv = [[GreenView alloc] initWithFrame:_viewGreen.frame];
[_viewGreen addSubview:gv];
}
Main Storyboard
OrangeView
GreenView (with multiple stackviews)
Overview
The contents of a .xib file are not "linked" to the class. That is, when you instantiate the class, that does not - by itself - also load the views and subviews you've laid out in the xib.
There are a couple ways to go about it. One method is to include the "load the elements" code inside your view class. That allows a more "conventional" approach of load/create a view instance, add as subview, set parameters (frame or constraints), etc.
Here is an example of subclassing UIView with the "xib-load" functions, and then making your actual class a sub-class of this "base":
//
// XIBViewBase.h
//
#import <UIKit/UIKit.h>
#interface XIBViewBase : UIView
#end
//
// XIBViewBase.m
//
#import "XIBViewBase.h"
#implementation XIBViewBase
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self xibSetup];
}
return self;
}
- (id)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
[self xibSetup];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self xibSetup];
}
- (void)xibSetup {
// make sure we don't add the subviews more than once
if (!self.subviews.count) {
UIView *view = [self loadFromXIB];
view.frame = self.bounds;
view.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:view];
[self sendSubviewToBack:view];
}
}
- (UIView *)loadFromXIB {
// Note: the .xib file MUST be named the same as the class for this to work
NSString *className = NSStringFromClass([self class]);
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
UINib *nib = [UINib nibWithNibName:className bundle:bundle];
UIView *v = [nib instantiateWithOwner:self options:nil].firstObject;
return v;
}
#end
I put together an example of this (using your target) that you can take a look at. Might help you get a handle on it:
https://github.com/DonMag/SimpleXIB
Hello StackOverflow.
I am trying to setup a UIView such that it loads itself from XIB
file in Xcode.
To do this, I went through the following initial steps:
Created empty UIView subclass.
Added a blank XIB file.
Then, I added all the subviews I wanted into the XIB file, and made corresponding IBOutlet Properties inside the UIView Subclass.
I watched this video to show me how to properly connect the outlets and set the files owner. The video instructs me to do the following things:
Do not set the UIView class to your subclass in XIB. Time link
Set the File's Owner to your UIView subclass in XIB:Time Link
Insert a IBOutlet into your UIView class of type UIView so your XIB file can load into that.Time link
Override initWithCoder like (image) if you intend to initialize the custom UIView within another XIB file.
Override initWithFrame like (image) if you intend to initialize the custom UIView programatically within another class file.
Cool, I have done all of these things, and am choosing to initialize my UIView programatically.
See my UIView subclass implementation file:
#import "CXHostsTableViewCellContentView.h"
#implementation CXHostsTableViewCellContentView
#pragma mark Custom Initializers
-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
[[NSBundle mainBundle]loadNibNamed:#"CXHostsTableViewCellContentView" owner:self options:nil];
[self setBounds:self.view.bounds];
[self addSubview:self.view];
}
return self;
}
-(instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
[[NSBundle mainBundle]loadNibNamed:#"CXHostsTableViewCellContentView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
And of course, my header file:
#import <UIKit/UIKit.h>
#import "CXStyleView.h"
#interface CXHostsTableViewCellContentView : UIView
#pragma mark UIView Properties
#property (strong, nonatomic) IBOutlet UIView *view;
#property (nonatomic,weak)IBOutlet UIView *standardView;
#end
I also have an image here of the XIB file's owner and another of the IBOutlet link from the base UIView to the outlet on file's owner.
Right, so everything's looking pretty good, should be no problem running this right?
Nope, whenever I initialize this subview and present it, I get a crash:
CXHostsTableViewCellContentView *scrollContentView = [[CXHostsTableViewCellContentView alloc]init];
I've really got no idea how to solve this, as I'm sure I'm following all of these steps right. I've googled and come across this question which has the same symptoms, but the answer has identical code to what I'm using, and this question with a contradictory reply.
I'm not sure what to do at this point, or what is causing the crash. I know that if I have NO outlets linked at all, it works. But then again, nothing displays either.
I think that You will face Problem When You Allocate Memory to Your scrollContentView object.
so,Try To allocate Memory With Frame.
i.e
Write this in .m file
- (void)myAllocation {
//do your stuff
}
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
[self myAllocation];
}
return self;
}
- (id)initWithCoder:(NSCoder*)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
[self myAllocation];
}
return self;
}
...
CXHostsTableViewCellContentView *scrollContentView = [[CXHostsTableViewCellContentView alloc] initWithFrame:CGRectMake(0, 10, 0, 20)];
I have created a custom subclass of UIView along with a xib file and declared IBOutlets and IBActions within the custom class.
#interface ContactUsView : UIView
#property (nonatomic, weak) IBOutlet UIButton *displayCloseButton;
- (IBAction)callButtonPressed:(id)sender;
- (IBAction)emailButtonPressed:(id)sender;
- (IBAction)displayCloseButtonPressed:(id)sender;
#end
In the xib file I have dragged in a UIView to represent my custom view. I have set:
Files owner = to my custom class
Have set the dragged in UIView to my custom class.
I have then added various buttons which are hooked up to the 3 methods stated above.
Inside the ContactUsView.m I have the following:
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame]) {
NSArray* array = [[NSBundle mainBundle] loadNibNamed:#"ContactUsView" owner:self options:nil];
for (id object in array) {
if ([object isKindOfClass:[ContactUsView class]])
self = (ContactUsView *)object;
}
}
return self;
}
When I come to create this view I do the following:
- (void)viewWillAppear:(BOOL)animated
{
ContactUsView *contactUs = [[ContactUsView alloc] initWithFrame:CGRectZero];
CGPoint origin = self.view.frame.origin;
CGSize size = self.view.frame.size;
[contactUs setFrame:CGRectMake(origin.x,
CGRectGetMaxY(self.view.frame) - 100,
size.width,
contactUs.frame.size.height)];
[self.view addSubview:contactUs];
}
Issue
When I press on one of the buttons the application crashes with:
Thread 1: EXC_BAD_ACCESS(code=2, address=0xb0c
Can anyone help me with this. I feel like I am probably making a mistake somewhere in regards to creating and loading custom uiviews from xibs.
If you require anymore information let me know. Many thanks.
Future reference
When creating a custom view using a xib DO NOT set the files owner. Instead create all your IBOutlets and IBActions as you normally would and then to hook them up open the Utilities tab and control drag from there.
• Files owner = to my custom class
Wrong. Files owner should be empty. The view itself is files owner. It means that you should connect all actions and outlets with ContactUsView in your xib.
[[NSBundle mainBundle] loadNibNamed:#"ContactUsView" owner:self options:nil]
...
self = (ContactUsView *)object;
After you passed self as ownerparameter. You changing it. Which means that previously allocated ContactUsView (self) will be destroyed since -loadNibNamed:owner:options: do not retain it. If you apply my first advice you should send nil as owner parameter
forloop here is not necessary use just array[0], because this is always your view if you have valid views hierarchy in your xib
If you are loading a UIView for an xib then you should create a class method to load the view.
In your customview.h
+(id)customView;
& in your customview.m
+ (id)customView
{
CustomView *customView = [[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:nil options:nil] lastObject];
if ([customView isKindOfClass:[CustomView class]])
return customView;
else
return nil;
}
You can initialize it anywhere using:
CustomView *myView = [CustomView customView];
EDIT: Make sure you have changed your customview's class in identity inspecter & also make sure your connection of IBActions are with that class' methods.
You can use delegate for this
this is how you can do this
#protocol CustomViewDelegate <NSObject>
- (void)callButtonPressed:(id)sender;
- (void)emailButtonPressed:(id)sender;
- (void)displayCloseButtonPressed:(id)sender;
#end
#interface ContactUsView : UIView
#property (nonatomic, weak) IBOutlet UIButton *displayCloseButton;
#property (nonatomic, weak) id<CustomViewDelegate> ButtonDelegate;
- (IBAction)callButtonPressed:(id)sender;
- (IBAction)emailButtonPressed:(id)sender;
- (IBAction)displayCloseButtonPressed:(id)sender;
#end
and in .m file
- (IBAction)callButtonPressed:(id)sender
{
[self.ButtonDelegate callButtonPressed:sender];
}
- (IBAction)emailButtonPressed:(id)sender{
[self.ButtonDelegate emailButtonPressed:sender];
}
- (IBAction)displayCloseButtonPressed:(id)sender{
[self.ButtonDelegate displayCloseButtonPressed:sender];
}
After that just set the delegate with viewcontroller refrence and use those delegate here
- (void)viewWillAppear:(BOOL)animated
{
ContactUsView *contactUs = [[ContactUsView alloc] initWithFrame:CGRectZero];
contactUs.ButtonDelegate = self;
CGPoint origin = self.view.frame.origin;
CGSize size = self.view.frame.size;
[contactUs setFrame:CGRectMake(origin.x,
CGRectGetMaxY(self.view.frame) - 100,
size.width,
contactUs.frame.size.height)];
[self.view addSubview:contactUs];
}
- (void)callButtonPressed:(id)sender
{}
- (void)emailButtonPressed:(id)sender
{}
- (void)displayCloseButtonPressed:(id)sender
{}
I have done this and works totlly fine
i have a .nib file in my application that i would like to show it to the user as a "popup" window.
I would like to have a result like this :
Here you can see an empty "popup" window with an X for closing.To be accurate this is an MT popup Window but sadly you can only load it with html and not with a view , like a .nib file.
Does anyone know how i could create a popup window like this to show a .nib file or have a ready 3rd party solution?
A custom UIView is what you want my friend.
So create a class that has the super UIView.
Create a an objective-c Class call. For example I call mine CustomView
Your Header(.h)
#import <UIKit/UIKit.h>
#interface CustomView : UIView
#end
Your class(.m)
#import "CustomView.h"
#implementation CustomView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
[self loadNib];
}
return self;
}
- (void) loadNib
{
NSArray * subviewArray = [[NSBundle mainBundle] loadNibNamed:#"MyNib" owner:self options:nil];
UIView * mainView = [subviewArray objectAtIndex:0];
[self addSubview:mainView];
}
- (IBAction)close:(id)sender{
[self removeFromSuperview];
}
#end
Now in your ViewController call and add your custom view. You many want this in a IBAction(button click etc).
CGRect frame = CGRectMake(10,10,300, 460);
CustomView *view = [[CustomView alloc] initWithFrame:frame];
[self.view addSubview:view];
When your custom view is added to your main view it will have loaded the nib.
EDIT:To have the x close it. In you customView have a IBACTION and just say
[self removeFromSuperView];
You may want to setup a delegate communicating with your controller to do something on when view on closes etc.
Want a to be able to tap the off custom view to close it, create a UIView put a UITapGestureRecognizer on it that closes your custom view and place it underneath your custom view and cover the whole screen.
Hope this helps,
BooRanger
I have a class that inherits from UIView, and this class has some controls that I have placed on it in IB.
Then, in the NIB file for my main view controller, I placed a view, and changed the class to my subclass, and created an outlet for the subclass. However, when I run my application, the app does not display the UI that I put on the subclass, it is just blank.
I am getting the initWithCoder and awakeFromNib messages in the subclass, here is what the subclass .m file basically looks like:
#import "AnalyticsDetailView.h"
#implementation AnalyticsDetailView
- (id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
return self;
}
- (void)awakeFromNib
{
[super awakeFromNib];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSArray *v = [[NSBundle mainBundle] loadNibNamed:#"AnalyticsDetailView" owner:self options:nil];
[self addSubview:[v objectAtIndex:0]];
}
return self;
}
- (void)dealloc
{
[super dealloc];
}
#end
I am not sure if the initWithFrame is correct, but since that method is not firing, I suspect that it doesn't matter at this point. If I put a breakpoint in my app after I have seen the subclass methods fire, I can look at the outlet subclass and the frame is the same as what I have created in IB.
Anyone have any suggestions (missing code, bad IB connections, etc.) on what to look for that I have missed or am doing incorrectly? Thanks.
To get your interface to appear, you'll need to explicitly instantiate a AnalyticsDetailView from your parent view controller.
So in somewhere like the viewDidLoad: or viewWillAppear: methods, you'll add a line that says:
AnalyticsDetailView * newView = [[AnalyticsDetailView alloc] initWithFrame: CGRectMake(x,y,height,width)];
[parentView addSubview: newView];
[newView release]; // subview retains for us