I'm following this video (except im linking the custom view in code not in IB), but I'll explain what's happening. I have created a custom view that extends off UiView. It is created from an xib file. Here is a photo of the xib file in interfacebuilder:
Notice that the file owner is already linked to my custom class.
Now I'll show you my custom class:
#import "CostcoLoginView.h"
#implementation CostcoLoginView
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if(self){
[[NSBundle mainBundle] loadNibNamed:#"CostcoLoginView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
#end
And here is the header file for my custom view:
#import <UIKit/UIKit.h>
#interface CostcoLoginView : UIView
#property (strong, nonatomic) IBOutlet UIView *view;
#end
notice the one property I have: it's a view that is linked to the interfacebuilder xib file shown in the photo above.
Everything looks good so far. My goal now is to use this custom UIView as the inputAccessoryView of a UITextField I have. Here is the partial code:
usernameField.inputAccessoryView = [[CostcoLoginView alloc] init ];
Why is this not working? Nothing appears. Is it because of how I'm initializing the custom view? When I check in the simulator nothing appears. I tried toggling the keyboard but nothing appears. The rest of app works fine, but my custom view does not appear.
You have not add subview when you call init,and you forget to set frame of your view in xib.
so you may change your code to this and try.
#import "CostcoLoginView.h"
#implementation CostcoLoginView
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
[self setup];
}
return self;
}
-(id)initWithCoder:(NSCoder *)aDecoder{
self=[super initWithCoder:aDecoder];
if(self){
[self setup];
}
return self;
}
-(id)init{
self = [super init];
if (self) {
[self setup];
}
return self;
}
-(void)setup{
[[NSBundle mainBundle] loadNibNamed:#"CostcoLoginView" owner:self options:nil];
self.view.frame = self.bounds;
[self addSubview:self.view];
}
#end
Related
I want to visualize XIB's in a storyboard using the IB_DESIGNABLE constant in Xcode 9.2 with Objective C targeting iOS SDK 11.2.
According this article from Apple the prepareForInterfaceBuilder method is called by the Interface Builder to let you do whatever shall only be done for the Interface Builder.
When Interface Builder instantiates a class with the IB_DESIGNABLE attribute, it calls this method to let the resulting object know that it was created at design time. You can implement this method in your designable classes and use it to configure their design-time appearance.
Based on this article I want to visualize XIB's in my storyboard. When I use a UIView element on a ViewController and set
first the custom class to XibView
second the nibName property to MainViewController, the name of the XIB file
I get the following buildtime error:
Main.storyboard:
error: IB Designables:
Failed to render and update auto layout status for MainViewController (8zi-kg-fjR):
The agent threw an exception.
What am I doing wrong that I get this error instead the visualized XIB in the View I use in the Main.storyboard?
This is the XibView.h
IB_DESIGNABLE
#interface XibView : UIView
#property (nonatomic, strong) IBOutlet UIView* contentView;
#property (nonatomic) IBInspectable NSString* nibName;
#end
This is the XibView.m
#import "XibView.h"
#implementation XibView
- (id)initWithCoder:(NSCoder *) coder
{
self = [super initWithCoder: coder];
if (self)
{
[self xibSetup];
}
return self;
}
//---------------------------------------------------------------------
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self xibSetup];
}
return self;
}
//---------------------------------------------------------------------
- (void)prepareForInterfaceBuilder
{
[super prepareForInterfaceBuilder];
[self xibSetup];
}
//---------------------------------------------------------------------
- (void)xibSetup
{
[[NSBundle mainBundle] loadNibNamed:_nibName owner:nil options:nil];
[self addSubview:self.contentView];
self.contentView.frame = self.bounds;
}
#end
Here's the method that I think has a bug:
- (void)xibSetup
{
[[NSBundle mainBundle] loadNibNamed:_nibName owner:nil options:nil];
[self addSubview:self.contentView];
self.contentView.frame = self.bounds;
}
The nib loader can't set self.contentView because you didn't pass self as the owner argument to loadNibNamed:options:. Try this instead:
[[NSBundle mainBundle] loadNibNamed:_nibName owner:self options:nil];
With the help of #E.Coms and still based on this article I figured it out what the problem was. To deliver an answer to be used for others here you can find the steps
How to visualize reusable xibs in storyboards using IB_DESIGNABLE with Objective-C?
Create a Xib holder objective-c header and class file (XibViewHolder)
Create a Xib file (MyView.xib)
Set the Xib's 'File's Owner' class to XibViewHolder
On your storyboard on any view controller add a UIView
Set that UIView's class to XibViewHolder class
Set that UIView's Interface Builder property called Nib Name, which is defined in the Xib holder class, to the name of the Xib you want to load e.g.: MyView.
This is the XibViewHolder.h
IB_DESIGNABLE
#interface XibViewHolder : UIView
#property (nonatomic, strong) UIView* contentView;
#property (nonatomic, strong) IBInspectable NSString* nibName;
#end
This is the XibViewHolder.m
#import "XibViewHolder.h"
#implementation XibViewHolder
- (void)awakeFromNib
{
[super awakeFromNib];
[self xibSetup];
}
- (void)prepareForInterfaceBuilder
{
[super prepareForInterfaceBuilder];
[self xibSetup];
[self.contentView prepareForInterfaceBuilder];
}
- (void)xibSetup
{
if (_nibName == nil) { return; }
UIView* loadedNibView = [self loadViewFromNib];
loadedNibView.frame = self.bounds;
loadedNibView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight);
[self addSubview:loadedNibView];
self.contentView = loadedNibView;
}
- (UIView*)loadViewFromNib
{
NSBundle* bundle = [NSBundle bundleForClass:[self class]];
UINib* nib = [UINib nibWithNibName:_nibName bundle:bundle];
return [nib instantiateWithOwner:self options:nil].firstObject;
}
#end
I am creating a custom UIView from xib called HeaderView. I have a UIButton in this HeaderView and on the button tap, i want a block to be called on ViewController in which the HeaderView is being added. Here is the code for my HeaderView.
Header File Code
#interface HeaderView : UIView
- (instancetype)initWithFrame:(CGRect)frame;
#property (copy) void(^seeAllHandler)();
#end
Implementation File Code
#interface HeaderView()
#property (weak, nonatomic) IBOutlet UILabel *titleLabel;
#property (weak, nonatomic) IBOutlet UIButton *seeAllButton;
#property (nonatomic, strong) UIView *contentView;
#end
#implementation HeaderView
- (instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setup];
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)coder
{
self = [super initWithCoder:coder];
if (self) {
}
return self;
}
- (void)awakeFromNib
{
[self.seeAllButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[self.seeAllButton setTitle:#"SEE ALL") forState:UIControlStateNormal];
[self.titleLabel setText:#"My Label")];
}
#pragma mark - Private methods
- (void)setup
{
//Setup view from the xib file.
self.contentView = [[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil] firstObject];
[self.contentView setFrame:self.bounds];
[self addSubview:self.contentView];
self.contentView.backgroundColor = [UIColor ccNordeaPink];
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
self.clipsToBounds = YES;
}
- (IBAction)sellAllTapped:(UIButton *)sender {
if(self.seeAllHandler != nil){
self.seeAllHandler();
}
}
#end
and here is my ViewController viewDidLoad method.
#interface ViewController () <UIScrollViewDelegate, UICollectionViewDataSource, UICollectionViewDelegate>
#property (nonatomic, strong) HeaderView *headerView;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupSubviews];
[self setupConstraints];
}
- (void)setupSubviews
{
self.headerView = [[HeaderView alloc] init ];
self.headerView.translatesAutoresizingMaskIntoConstraints = NO;
self.headerView.seeAllHandler = ^{
DDLogDebug(#"See all Tapped");
};
[self.view addSubView: self.headerView];
}
The problem is that when the button is tapped the assigned block is nil and hence it is not called.
So what's happening is that you are assigning the block in viewDidLoad (via the setupSubviews method) of the ViewController. I can see that you are programmatically instantiating a HeaderView. So when you are assigning the block you are actually sending a message to an instance.
But, you are also inflating another instance by calling loadNibNamed inside the setup method of HeaderView. And that HeaderView does not have your block, and that's the one that gets shown in the UI. You've kind of got another HeaderView inside the contentView of your HeaderView.
Consequently, the actual/on-screen HeaderView instance's handler property is nil, so when it tries to fire the block back to you it is working with nil too.
The best way to approach what's happening is go into HeaderView and set a breakpoint in awakeFromNib and another breakpoint in setup. You'll see that setup gets called first. If you po self in lldb you will the address of the current instance. The next thing that happens is that awakeFromNib gets called and it hits the breakpoint there. And if you do po self again, you'll see a different address. A different instance! This is the one you are seeing in the UI. And it doesn't have the handler set!
If you want to keep this methodology for loading view hierarchy, then a simple fix is to instantiate the HeaderView from nib inside your ViewController.
So instead of doing the self.headerView = [[HeaderView alloc] init], you do this:
self.headerView = [[[NSBundle mainBundle] loadNibNamed:#"HeaderView" owner:self options:nil] firstObject];
I've really spent my hours for finding any approach about this problem.
I have;
CustomView.h that extends from UIView and is also IB_DESIGNABLE
CustomView.m for implementation of this view with override methods init, initWithCoder and initWithFrame
CustomView.xib that bound to CustomView class with all properties
And, I have :
CustomTableViewCell.h that extends from UITableViewCell
CustomTableViewCell.m that implementation of this cell
CustomTableViewCell.xib that bound to CustomTableViewCell class with all properties&outlets.
CustomTableViewCell.xib includes CustomView...
There is no any error, but the area of CustomView remains blank.
For view you need to do some work around.
Create a property and link your view in the XIB to it;
#property (strong, nonatomic) IBOutlet UIView *xibView;
Then have something like this:
- (void)awakeFromNib{
[super awakeFromNib];
[self commonInit];
}
- (instancetype)init{
self = [super init];
if (self) {
[self commonInit];
}
return self;
}
- (instancetype)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if (self) {
[self commonInit];
}
return self;
}
- (void)commonInit{
//this will work if the view has the same name as the XIB
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
self.xibView.frame = self.bounds;
[self addSubview:self.xibView];
self.xibView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}
Basically you have to load the XIB manually and add it to your custom view.
I have UITableView is Storyboard.
I try to create UITableViewCell in Xib and register it in UITableView as that:
-(void)viewDidLoad{
[super viewDidLoad];
UINib *nib = [UINib nibWithNibName:#"FlipperTableViewCell" bundle:nil];
[self.tableView registerNib:nib forCellReuseIdentifier:#"fliperCell"];
}
It work perfectly.
But this cell must have one view inside and flip it to another view when button on first view pressed.
I create another two views in same xib.
File's owner is FliperTableViewCell class. Class of first view is FliperTableViewCell.
But now a can't figure out how to add second view (UIView) as subview in first view (UITableViewCell), which loaded by table view.
I try to get second view from loadNibNamed:, but it became infinite cycle.
#implementation FlipperTableViewCell
-(id)initWithCoder:(NSCoder *)aDecoder{
if (self = [super initWithCoder:aDecoder]){
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil] objectAtIndex:1]];
}
return self;
}
What is another way to get second view from same xib and add it as subview when first view loaded?
problem was solved when I replace
[self.tableview registerNib:nib forCellReuseIdentifier:#"FlipperTableViewCell"];
to
[self.tableview registerClass:[FlipperTableViewCell class] forCellReuseIdentifier:#"FlipperTableViewCell"];
then remove first view from xib and rewrite FlipperTableViewCell to initiate in code
#import "FlipperTableViewCell.h"
#interface TableViewCell ()
#property (nonatomic, strong) UIView *firstView;
#property (nonatomic, strong) UIView *secondView;
#end
#implementation FlipperTableViewCell
-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier{
if (self = [super initWithStyle:style reuseIdentifier:reuseIdentifier]){
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class) owner:self options:nil];
self.firstView = nibObjects[0];
self.secondView = nibObjects[1];
[self.contentView addSubview:self.firstView];
self.selectionStyle = UITableViewCellSelectionStyleNone;
}
return self;
}
// this is for test changing views
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
if (self.contentView.subviews[0] == self.firstView){
[self.firstView removeFromSuperview];
[self.contentView addSubview:self.secondView];
}
else{
[self.secondView removeFromSuperview];
[self.contentView addSubview:self.firstView];
}
}
#end
I am trying to subclass a UIView using the nib. Using the following code:
- (void)awakeFromNib
{
[super awakeFromNib];
NSArray *v = [[NSBundle mainBundle] loadNibNamed:#"Qus_Scale1to7View" owner:self options:nil];
[self addSubview:[v objectAtIndex:0]];
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
NSArray *v = [[NSBundle mainBundle] loadNibNamed:#"Qus_Scale1to7View" owner:self options:nil];
[self addSubview:[v objectAtIndex:0]];
}
return self;
}
this creates the object correctly and view also displayed and when the object loads from its nib the delegate instantly becomes null and ignores any attempt to assign values to it.
Can anyone know the reason why it is?
Thanks in advance.
It won't work reusing same xib for multiple view controllers. If you want to reuse that view make a class that inherits from UIView and add the code there.
#import "SomeProtocol.h"
#interface MyCustomView : UIView {
IBOutlet UIView *headerView;
IBOutlet UIView *footerView;
IBOutlet UIButton *updateBtn;
}
#property (nonatomic, assign) id<SomeProtocol> delegate;
#end
............
#implementation BCFirmwareView
#synthesize delegate = _delegate;
+ (id)viewFromNibWithName: (NSString*)name {
UIView *view = nil;
NSArray *views = [[NSBundle mainBundle] loadNibNamed: name owner: self options: nil];
if (views) {
for (UIView *aView in views) {
if ([aView isKindOfClass: NSClassFromString(name)])
view = aView;
}
}
return view;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder: aDecoder];
if (self) {
}
return self;
}
- (id)init {
self = [[MyCustomView viewFromNibWithName: #"MyCustomView"] retain];
if (self) {
}
return self;
}
- (void)dealloc {
self.delegate = nil;
[headerView release];
[footerView release];
[updateBtn release];
[super dealloc];
}
- (void)awakeFromNib {
[super awakeFromNib];
// Do any additional setup after loading the view from its nib.
headerView.backgroundColor = [UIColor redColor];
footerView.backgroundColor = [UIColor greenColor];
}
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview: newSuperview];
if (!newSuperview)
return;
}
- (void)didMoveToSuperview {
[super didMoveToSuperview];
}
- (IBAction)updateBtnPressed: (id)sender {
// do some stuff
}
#end
The next step is to open the xib in Interface Builder and set your class as the custom class for the view, not for the File's Responder. Right click on the view and make the outlet and actions connections.
Now you should be able to simply make instances of your MyCustomView in any view controller and use it. Will work from Interface Builder as well if you don't forget to change your view custom class to your class.
You can create a custom UIView with Xib and add properties to it.
Then you link the class with the xib and link the properties with the IB.
Or you can only use the
NSArray *v = [[NSBundle mainBundle] loadNibNamed:#"Qus_Scale1to7View" owner:self options:nil];
UIView *view = [v objectAtIndex:0];
and set your objects values using viewWithTag: method.
UILabel *label = (UILabel *)[view viewWithTag:yourTag];
Let me know if this helps.