I'm trying to essentially load a nib into view when a certain criteria is met. I'm able to show the nib on my storyboard thought I cant seem to be able to execute any actions when the button is pressed.
Just to express my ultimate goal. I would like to create sort of a template with my nib view and then display the nib view from my storyboards but manipulate the labels in my nib view with values sent from the storyboard.
OK so I'll do my best to try and show my detail steps on how i try to accomplish this.
Firstly here are is a picture of my project.
my VCDemoView.h
#import <UIKit/UIKit.h>
#interface AppDelegate : UIResponder <UIApplicationDelegate>
#property (strong, nonatomic) UIWindow *window;
#end
VCDemoView.m
#import "VCDemoView.h"
#interface VCDemoView ()
#property(nonatomic) IBOutlet UIImageView *imageView;
#property(nonatomic) IBOutlet UILabel *titleLabel;
#property(nonatomic) IBOutlet UILabel *subtitleLabel;
#property(nonatomic) IBOutlet UIView *container;
//- (IBAction)changeLabel:(id)sender;
#end
#implementation VCDemoView
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self == nil) return nil;
[self initalizeSubviews];
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self == nil) return nil;
[self initalizeSubviews];
return self;
}
-(void)initalizeSubviews
{
//Load the contents of the nib
NSString *nibName = NSStringFromClass([self class]);
UINib *nib = [UINib nibWithNibName:nibName bundle:nil];
[nib instantiateWithOwner:self options:nil];
//Add the view loaded from the nib into self.
[self addSubview:self.container];
}
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
- (IBAction)changeLabel:(id)sender {
//[self.subtitleLabel.text = #"MIGUEL"];
_subtitleLabel.text = #"miguel";
}
#end
storyboard view controller.
#import "ViewController.h"
#import "VCDemoView.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
VCDemoView * CustomView = [[VCDemoView alloc]init] ;//[[addMyLocation alloc]init];
[self.view addSubview:CustomView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
Can anyone spot what I'm doing wrong? I cant seem to be able to communicate with the button? I'm sure is something silly.
here is a link to the dropbox project with all the source code if you prefer to see it that way.
https://www.dropbox.com/sh/23cbhs4qvsxwei0/AADFs6MN3eqJNK42Io2etuJea?dl=0
Thanks is advance guys.
You never set the frame of your VCDemoView. The frame defaults to CGRectZero.
A view doesn't clip its subviews by default, so you can still see the labels and the button, even though they're outside the bounds of the VCDemoView. But a view does not receive events outside its bounds, so when you tap on the button, the top-level view swallows the event because it finds that the event is outside the bounds of its one subview (the VCDemoView).
You can fix the frame of your VCDemoView like this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
VCDemoView * CustomView = [[VCDemoView alloc]init] ;//[[addMyLocation alloc]init];
CustomView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
CustomView.frame = self.view.bounds;
Note that you can use autoresizing even though your storyboard uses autolayout constraints. Autolayout will turn the autoresizing mask into constraints for you.
Or you could just put your VCDemoView in your storyboard as a child of the root view and set up the constraints there.
You should not call and action inside a View this what the Controller should handle .
You need to change the fileOwner to be the controller and the View Class to be VCDemoView , connect the View to the controller in the IB , and load the nib from there , then add it as a subView to the ViewController's view .
this the edited code ,, I hope it helps .. https://dl.dropboxusercontent.com/u/33359624/demo-SO/Archive.zip
You are not set the CustomView frame so the default frame is CGRectZero. if you set the frame of the CustomView it will works fine.
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
VCDemoView * CustomView = [[VCDemoView alloc]init] ;
CustomView.frame =self.view.frame;
[self.view addSubview:CustomView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
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
My files owner has 3 outlets, and all of them are nil on viewDidLoad...
__weak IBOutlet UIView *mainImagePlaceholderView;
__weak IBOutlet UILabel *mainImageLabel;
__weak IBOutlet UIView *imageFilmstripPlaceholderView;
These are located in my .m file, ...
#implementation iRpImageViewerViewController
{
...
__weak IBOutlet UIView *mainImagePlaceholderView;
__weak IBOutlet UILabel *mainImageLabel;
__weak IBOutlet UIView *imageFilmstripPlaceholderView;
...
}
My class in question is iRpImageViewerViewController.xib
In IB, the 'files owner custom class' is 'iRpImageViewerViewController.m'
In IB, the nib 'view custom class' is UIView
Everything shows as hooked up properly.
I have cleaned my build folder.
I have cleared my derived data folder.
I even tried to trick the views into loading by referring to them in the init method and setting their backcolor.
-(void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
theMainImageGridviewControl = [[IGGridView alloc]initWithFrame:mainImagePlaceholderView.frame style:IGGridViewStyleSingleCellPaging];
// Set additional properties to configure the grid view
theMainImageGridviewControl.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
theMainImageGridviewControl.selectionType = IGGridViewSelectionTypeCell;
theMainImageGridviewControl.contentInset = UIEdgeInsetsZero;
theMainImageGridviewControl.allowHorizontalBounce = NO;
theMainImageGridviewControl.alwaysBounceHorizontal = NO;
theMainImageGridviewControl.alwaysBounceVertical = NO;
[mainImagePlaceholderView addSubview:theMainImageGridviewControl];
// Setting a breakpoint here reveals that all the outlets are nil
[self addAChildViewController:theFilmstripViewController toViewWithTag:imageFilmstripPlaceholderView.tag];
[self constructAndConfigurePrimaryImageMarker];
}
Not sure exactly how you created the XIB and the view controller, but did you try adding the following to the initializer that you calling for the view controller:
self = [super initWithNibName:#"iRpImageViewerViewController" bundle:nil];
I'm trying to create a custom UIView which holds references to its own IBOutlets. I then want to put this custom UIView into another nib.
I'm doing some additional logic in the custom UIView's awakeFromNib method. Unfortunately, when I try to access the IBOutlets in awakeFromNib, they are nil.
Here's the setup:
I have a UIView subclass, CustomView.
I have a custom .xib file with three subviews
In the other nib (that belongs to the view controller), I have dragged a UIView onto the view, and then changed the custom class to CustomView.
I tried setting the view in the CustomView nib in IB to a custom class CustomView and connecting the IBOutlets to the view, but they were still nil.
I tried setting file owner to CustomView and connecting the IBOutlets to file's owner, but they were still nil.
I also tried using another IBOutlet UIView *view and then adding that as a subview to self in awakeFromNib but that also didn't do anything.
Here's the code:
// CustomView.h
#interface CustomView : UIView
#property (nonatomic, retain) IBOutlet UITextField *textField;
#property (nonatomic, retain) IBOutlet UIView *subview1;
#property (nonatomic, retain) IBOutlet UIView *subview2;
// CustomView.m
#implementation CustomView
#synthesize textField, subview1, subview2;
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Fails because self.textField is nil
self.textField.text = #"foo";
}
I ended up using the steps in the most recent edit here and they worked beautifully.
You use a plain UIView as the top level view in the xib.
You then set file's owner to the custom subclass (CustomView).
Finally, you add a line:
[self addSubview:[[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0]];
in the if (self != nil) block in both initWithCoder and initWithFrame.
Voila! The IBOutlets are hooked up and ready to go after the call. Really pleased with the solution, but it was very difficult to dig up.
Hope this helps anyone else.
EDIT: I updated the link to one that isn't dead. Since I never spelled out the full code, here is what it looks like after modification:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
UIView *nib = [[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
UIView *nib = [[[UINib nibWithNibName:#"CustomView" bundle:nil] instantiateWithOwner:self options:nil] objectAtIndex:0];
[self addSubview:nib];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self setup];
}
- (void)setup {
// Doesn't fail because life is awesome
self.textField.text = #"foo";
}
This pattern has become so common that I actually created a category on UIView called UIView+Nib, which implements the following method:
+ (UIView *)viewWithNibName:(NSString *)nibName owner:(id)owner {
return [[[UINib nibWithNibName:nibName bundle:nil]
instantiateWithOwner:owner options:nil]
objectAtIndex:0];
}
So the above code can be simplified to:
[self addSubview:[UIView viewWithNibName:#"CustomView" owner:self]];
Note also that the above code can be refactored even more, since the logic is exactly the same in initWithFrame: and initWithCoder:. Hope that helps!
As in Dr. Acula's answer, This is probably because custom view's nib is lazy loaded when loaded from another nib (Nested nib loading), so we need to instantiate it manually. In swift code will look like this :
override func awakeFromNib() {
super.awakeFromNib()
self.customview = UINib(nibName: "CustomViewNib", bundle: nil).instantiateWithOwner(self, options:nil)[0] as! UIView
self.customview?.frame = self.viewContainer.bounds
self.viewContainer.addSubview(self.customview!)
}
I am assuming the XIBs' structure is something like this
CustomView.xib
CustomView
UITextField -> linked to IBOutlet textField
other views
CustomViewController.xib
CustomView
If this is right, then your CustomView will be created but as it is not read from CustomView.xib it doesn't have any IBOutlets assigned.
However, if your CustomViewController.xib looks like following
CustomViewController.xib
CustomView
UITextField -> linked to IBOutlet textField of CustomView
then this should work. The IBOutlet of CustomView instance should be set by the CustomViewController.xib.
Better than setting any IBOutlets in the CustomViewController.xib would be to implement awakeAfterUsingCoder: in your CustomView and create a replacement object by loading your CustomView.xib in there. This way your CustomView remains truly custom and you don't have to edit other XIBs to change the structure, add/remove IBOutlets, etc.
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...
}
I am working on a very basic app that displays a popover when the user is entering text into a UITextField. Unfortunately, the popover is not showing up and the default keyboard is appearing (which shouldn't). Here is my relevant code below:
NumberPadViewController.h
#import <UIKit/UIKit.h>
#import "NumberViewController.h"
#interface NumberPadViewController : UIViewController <UITextFieldDelegate> {
IBOutlet UITextField *numTest;
}
#property (nonatomic, strong) NumberViewController *numberPicker;
#property (nonatomic, strong) UIPopoverController *numberPickerPopover;
#end
NumberPadViewController.m
- (BOOL)textFieldShouldBeginEditing:(UITextField *) textField
{
// Create popover controller if nil
if(_numberPickerPopover == nil){ //make sure popover isn't displayed more than once in the view
_numberPickerPopover = [[UIPopoverController alloc]initWithContentViewController:_numberPicker];
}
[_numberPickerPopover presentPopoverFromRect:numTest.frame inView:self.view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
return NO;
}
My popover class is called NumberViewController.h
#interface NumberViewController : UIViewController {
}
#property (strong, nonatomic) IBOutlet UIButton *oneButton;
NumberViewController.m
#import "NumberViewController.h"
#interface NumberViewController ()
#end
#implementation NumberViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
NSInteger buttonHeight = _oneButton.frame.size.height * 4;
NSInteger buttonWidth = _oneButton.frame.size.width * 3;
self.contentSizeForViewInPopover = CGSizeMake(buttonWidth, buttonHeight);
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I have created the UITextField in Storyboard, and set the delegate there. Can anyone see what I am doing wrong?
Thanks in advance to all who reply.
Ensure the textFieldShouldBeginEditing delegate method is being called. The rest of the code looks correct.
This is just a thought, but when a popover does a popover thing i think it becomes first responder, but your text view is first responder... so it might not be able to over do it.... if this is the error then before you tell the popover to appear you can say [textView resignFirstResponder]; and see if that helps.... it's just a thought though not 100% sure i will have to do some testing ~
also check to see if _numberPicker is not nil aswell i don't know what happens if you try to display a popover with no controller but you can see if that's the problem
Seeing as your popover isn't represented in the storyboard (if I read your post right) I think you need to add the popover view as a subview in code. Something like:
[self addSubview:_numberPickerPopover];
There are potentially a few places to do this. Probably makes the most sense in your textFieldShouldBeginEditing: method, after you've inited it.