I need to subclass a UITabBarController so that I can completely replace the UITabBar view with a custom view that I can hopefully produce in the interface builder. I tried but am not succeeding.
First, I created a subclass of UITabBarController along with a xib. I deleted the default view in the xib, and replaced it with a new one that was only 60px tall (the size of my tabbar). I dragged the necessary buttons onto it, and configured the .h file like so:
#interface ToolbarViewController : UITabBarController
#property (strong, nonatomic) IBOutlet UIView *tabBarView;
#property (strong, nonatomic) IBOutlet UIButton* firstButton;
#property (strong, nonatomic) IBOutlet UIButton* secondButton;
#end
My xib looks like this:
When I launch the app, I see an empty space at the bottom made for the tab bar, but I am not seeing an actual tab bar:
Update: I realize that I'm not actually launching the xib file in the .m file. Anyone know how I can do this properly?
There are various different solutions for adding a custom set of buttons to a custom tab bar controller subclass. I've done it years ago following this guide: http://idevrecipes.com/2010/12/16/raised-center-tab-bar-button/.
The idea is to add a custom UIView over the tab bar of your UITabBarController subclass. The CustomTabBarController class doesn't have to have a xib. Instead, I have a subclass of UIView that can either be programmatically laid out, or created using a xib for a UIView. Here's the header file for my CustomTabBarView class:
#interface CustomTabBarView : UIView
{
CALayer *opaqueBackground;
UIImageView *tabBG;
IBOutlet UIButton *button0;
IBOutlet UIButton *button1;
IBOutlet UIButton *button2;
NSArray *tabButtons;
int lastTab;
}
#property (nonatomic, weak) id delegate;
-(IBAction)didClickButton:(id)sender;
You'll either connect the desired buttons to button0, button1, button2, etc in the xib file, or do it programmatically on init for the view. Note that this is the UIView subclass.
In CustomTabBarView.m:
-(IBAction)didClickButton:(id)sender {
int pos = ((UIButton *)sender).tag;
// or some other way to figure out which tab button was pressed
[self.delegate setSelectedIndex:pos]; // switch to the correct view
}
Then in your CustomTabBarController class:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
tabView = [[CustomTabBarView alloc] init];
tabView.delegate = self;
tabView.frame = CGRectMake(0, self.view.frame.size.height-60, 320, 60);
[self.view addSubview:tabView];
}
When the buttons are clicked in the CustomTabBarView, it will call its delegate function, in this case the CustomTabBarController. The call is the same function as if you clicked on a tab button in the actual tab bar, so it will jump to the tabs if you have set up the CustomTabBarController correctly like a normal UITabBarController.
Oh, on a slightly separate note, the correct way to add a custom xib as the interface for a subclass of UIView:
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
NSArray *subviewArray = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:self options:nil];
UIView *mainView = [subviewArray objectAtIndex:0];
//Just in case the size is different (you may or may not want this)
mainView.frame = self.bounds;
[self addSubview:mainView];
}
return self;
}
In the xib file, make sure the File's Owner has its Custom class set as CustomTabBarView.
Related
I'm having a problem that doesn't seem to have an obvious solution. I've searched around and I've been through all common answers that I could find.
My custom xib views don't show up on the app when I launch. The background is clear, and this xib has 5 image views as you can see below which aren't set to hidden.
The class has about 5 delegates as well which I set from a caller when the delegates have been initialized. The caller of initWithDelegates: is the parent UIViewController that displays this xib.
CustomView.h
#interface CustomView : UIView<SomeProtocol>
// UI Items - THESE AREN'T SHOWING UP
#property (strong, nonatomic) IBOutlet UIImageView *image1;
#property (strong, nonatomic) IBOutlet UIImageView *image2;
#property (strong, nonatomic) IBOutlet UIImageView *image3;
#property (strong, nonatomic) IBOutlet UIImageView *image4;
#property (strong, nonatomic) IBOutlet UILabel *label;
#property (strong, nonatomic) IBOutlet UIStackView *centerStackView;
- (id)initWithDelegates:(UIViewController*)delegate1 withDelegate2:(NSObject<Delegate2>*)delegate2 withDelegate3:(NSObject<Delegate3>*)delegate3 withDelegate4:(UIViewController<Delegate4>*)delegate4
withDelegate5:(NSObject<Delegate5>*)delegate5;
#end
CustomView.m
#implementation CustomView
- (void)awakeFromNib {
[super awakeFromNib];
}
- (id)initWithDelegates:(UIViewController*)delegate1 withDelegate2:(NSObject<Delegate2>*)delegate2 withDelegate3:(NSObject<Delegate3>*)delegate3 withDelegate4:(UIViewController<Delegate4>*)delegate4
withDelegate5:(NSObject<Delegate5>*)delegate5
{
NSArray * arr =[[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:nil options:nil];
self = [arr firstObject];
self.delegate1 = delegate1;
self.delegate2 = delegate2;
self.delegate3 = delegate3;
self.delegate4 = delegate4;
self.delegate5 = delegate5;
[self setLoopImage];
[self layoutIfNeeded];
return self;
}
#end
What else I've verified:
I made sure that the xib file is properly selected under "Custom Class" in the interface builder.
I've walked through the code to make sure there are no crashes or obvious issues.
The xib doesn't appear to have any obvious issues in interface builder such as being set to HIDDEN.
The 5 IBOutlet ImageViews get a memory address and aren't nil.
Any help is appreciated!
UPDATE:
I believe this has something to do with putting the nibs in a nested UIView. In the interface builder, the order is UIViewController -> UIView -> nibs
There doesn't seem to be a method to register nibs from the parent UIViewController as there are in classes like UITableView.
I've also tried:
Setting the background color in the xib to make sure it shows up. It doesn't. However if I set the background color to red in the parent where I added the view in interface builder, it turns red. (See Figure 1), and my xib file is shown in Figure 2
Over-rode initWithFrame to make sure its being called. It is called.
Thinking that the view may be instantiated twice, one via autolayout and one via my custom init method, I tried making the (id)initWithDelegates method not init or register the nib in case that was duplicating the object. I did this by removing 1) return self, 2) the lines that register the nib, and 3) making the method prototype return void - I.E. (void)initWithDelegates - This didn't work either.
I tried these lines within initWithDelegates :
[self addSubview: self.image1];
[self bringSubviewToFront: self.image1];
[self.menuImage setHidden:image1];
-- with no luck.
Figure 1: (Actual result)
Figure 2 (Expected.. with red background)
Check your Autolayout in your custom XIB. Something the contain does not show because of it. For example: only center vertically & center horizontally. Try to do different autolayout constraint in your view in custom class.
I think something is wrong with layout of your CustomView inside ParentView. Could you please:
Check the frame of CustomView when you add it into the ParentView.
Check constraints that has CustomView inside ParentView. If you don't
have them - add it, constraints will iOS the glue how to layout your
CustomView. More information about this you can find here Programmatically Creating Constraints
And also you can try to use "Debug View Hierarchy" in Xcode to
inspect what is going on on your screen, there you can find your
CustomView in the view hierarchy and you will see what is wrong.
nibs cannot be nested within the structure.
Instead of:
UIViewController -> UIView -> nibs..
It should be:
UIViewController -> Container View -> UIView with nibs.
I am still learning interface builder.
I created a xib containing a button, a progress bar and a spinner. Lets call this xib MyToolbar.xib. I created classes for that xib (MyToolbar.h and .m). Inside the xib, I set the toolbar’s class to MyToolbar. Inside the xib I set File’s Owner to MyToolbar. I connected outlets to all elements and put them on the header.
The class header is this:
#import <UIKit/UIKit.h>
#interface MyToolbar : UIToolbar
#property (weak, nonatomic) IBOutlet UIButton *button;
#property (weak, nonatomic) IBOutlet UIActivityIndicatorView *spinner;
#property (weak, nonatomic) IBOutlet UIProgressView *progressBar;
#end
this is the implementation
#import “MyToolbar.h"
#implementation MyToolbar
- (id)init {
self = [super init];
if (self) {
self = [[[NSBundle mainBundle] loadNibNamed:#"MyToolbar"
owner:self
options:nil]
objectAtIndex:0];
}
return self;
}
Now I create the object on the main class
MyToolbar *toolbar = [[MyToolbar alloc] init];
at this point, toolbar is not nil but toolbar.button, toolbar.progessBar and toolbar.spinner are all nil.
I know the elements are not instantiated unless you display them.
Is there a way to make this work without displaying the object?
Did you connect the views to File's owner outlet? Try to remove the File's owner or connections and connect the views to MyToolbar.h outlets. I tried and it's working.
Right now I have a custom view class called OTGMarkerDetailView which inherits from UIView and a corresponding .xib with it. It just has two text labels and I've linked the text labels to the text label IBOutlets in OTGMarkerDetailView.m.
OTGMarkerDetailsView.h:
#import <UIKit/UIKit.h>
#interface OTGMarkerDetailView : UIView
- (void)setLabelsWithMainAddress:(NSString *)mainAddress subAddress:(NSString *)subAddress;
#end
OTGMarkerDetailView.m
#import "OTGMarkerDetailView.h"
#interface OTGMarkerDetailView ()
#property (nonatomic, strong) IBOutlet UILabel *mainAddressLabel;
#property (nonatomic, strong) IBOutlet UILabel *subAddressLabel;
#end
#implementation OTGMarkerDetailView
- (void)setLabelsWithMainAddress:(NSString *)mainAddress subAddress:(NSString *)subAddress {
NSLog(#"%#", self.mainAddressLabel.text);
self.mainAddressLabel.text = mainAddress;
self.subAddressLabel.text = subAddress;
NSLog(#"%#", self.mainAddressLabel.text);
}
#end
I load it in another view as a subview, using initWithFrame. But the console always logs null when I try to set the text label values, and when I use a breakpoint it seems the mainAddressLabel and the subAddressLabel are nil themselves. Did I do something wrong in linking the xib to the view? What am I missing? Thanks.
I found a work around. I have created a custom UIView.
1.
I attached Nib file to it in initWithFrame method
CustomView *nibView;
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
NSArray *array = [[NSBundle mainBundle] loadNibNamed:#"CustomView" owner:self options:nil];
nibView = [array objectAtIndex:0];
[self addSubview:nibView];
}
return self;
}
You can see clearly, I haven't created the instance of UIView instead I created nibView of the same class type.
2.
Now creating IBOutlet properties and work on it. In customView.m file.
#interface FTEndorsedExpandedView : UIView
#property (retain) IBOutlet UILabel *label;
#end
3.
Create functions to set title or changing properties. (in customView.m file). Use nibView to access the properties rather than using self.label
-(void)setLabelText:(NSString*)string{
[nibView.label setText:string];
}
When you create your custom view in another view using initWithFrame a new instance of your custom class is created. This instance is not the same one you have in interface builder and hence the label properties are nil for this newly created instance. In order to solve this problem either put your view in its parent view in interface builder with its connection attached or override initWithFrame for your custom view and initialise your labels in there.
I know this is quite straight forward but after too much hair-pulling I am nowhere near solution.
I have seen tutorials explaining how to create view using XIB and all. But none of them address the situation that I have here.
I have an XIB file, a custom UIView subclass that has few labels and buttons. The UIView subclass is reusable, and that is the reason I can't have outlets inside any single View controller. As a result I store individual controls (subviews) of this view inside my custom UIView itself. This is logical, as no view controller should own the subviews of this custom view which is to be included in every view controller.
The problem is, I don't know how to initialize the entire UI fully.
Here is my code for UIView Subclass:
#interface MCPTGenericView : UIView
+(id)createInstance : (bool) bPortrait;
#property (weak, nonatomic) IBOutlet UIView *topView;
#property (weak, nonatomic) IBOutlet UIView *titleView;
#property (weak, nonatomic) IBOutlet UILabel *titleLabel;
#property (weak, nonatomic) IBOutlet UIButton *logoButton;
#property (weak, nonatomic) IBOutlet UITextField *searchTextField;
#property (weak, nonatomic) IBOutlet UIButton *menuButton;
#end
Later on, I also plan to use this same XIB file for landscape orientation of this UIView too, and I plan to use the same above outlets with landscape oriented controls in same XIB.
And here is the implementation:
#implementation MCPTGenericView
//#synthesize topView, titleLabel, titleView;
+(id)createInstance : (bool) bPortrait
{
UIView * topLevelView = nil;
MCPTGenericView * instance = [MCPTGenericView new];
NSArray * views = [[NSBundle mainBundle] loadNibNamed:#"MoceptGenericView" owner:instance options:nil];
int baseTag = (bPortrait)?PORTRAIT_VIEW_TAG_OFFSET:LANDSCAPE_VIEW_TAG_OFFSET;
// make sure customView is not nil or the wrong class!
for (UIView * view in views)
{
if (view.tag == baseTag)
{
topLevelView = view;
break;
}
}
instance.topView = (MCPTGenericView *)[topLevelView viewWithTag:baseTag + 1];
instance.searchTextField = (UITextField *)[topLevelView viewWithTag:baseTag + 2];
instance.menuButton = (UIButton *)[topLevelView viewWithTag:baseTag + 3];
instance.logoButton = (UIButton *)[topLevelView viewWithTag:baseTag + 4];
instance.titleView = [topLevelView viewWithTag:baseTag + 5];
instance.titleLabel = (UILabel *)[topLevelView viewWithTag:baseTag + 6];
return instance;
}
-(id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"MCPTGenericView" owner:self options:nil] objectAtIndex:0]];
}
return self;
}
-(void)awakeFromNib
{
[super awakeFromNib];
[self addSubview: self.titleView];
[self addSubview:self.topView];
}
- (id) init
{
self = [super init];
if (self)
{
[[NSBundle mainBundle] loadNibNamed:#"MCPTGenericView" owner:self options:nil];
[self addSubview:self.topView];
[self addSubview:self.titleView];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
// Initialization code
[[NSBundle mainBundle] loadNibNamed:#"MCPTGenericView" owner:self options:nil];
[self addSubview:self.topView];
[self addSubview:self.titleView];
}
return self;
}
/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
// Drawing code
}
*/
#end
Something that worked:
I succeeded in calling initWithFrame:frame from my viewcontroller. That way, I could see all controls properly initialized. But then, why should I be supplying a frame if I have already drawn an XIB? Shouldn't loadNibNamed be handling frame setting and layout stuff since that is the intended use of XIBs?
I am also baffled at the way loadNibNamed needs an owner object. Why do we already need an object to get the same object from XIB? That too, a half-baked one?
Please help...
What was baffling me was the way loadnibnamed loses xib layout & outlet information. I finally found a way to achieve it.
Here is a recap of what works:
1) Suppose MyCustomView is your custom view class - you design it and its subviews as part of XIBs. You do this via interface builder, so self-explanatory.
2) Add MyCustomView.h and MyCustomView.m (boilerplate) via Xcode -> File -> New -> Objective C Class.
3) Next, within MyCustomView.xib, set File's Owner = MyCustomView (class name just added). Do not touch top most View's custom class - leave it as UIView. Else it will end up in recursion!!!
4) In MyCustomView.h, create few outlets corresponding to subviews within MyCustomView.xib.
Such as:
#property (weak) IBOutlet UILabel * label1;
#property (weak) IBOutlet UIButton * button1;
5) Go to MyCustomView.xib. Select each subview (label, button), right click, drag from "New Referencing Outlet" and drag it up to File's Owner.
This will popup a list of outlets matching the subview's type from where you have dragged. If you dragged from a label, it will pop up label1, and so on. This shows that all you did up to this step is correct.
If you, on the other hand, screwed up in any step, no popup will appear. Check steps, especially 3 & 4.
If you do not perform this step correctly, Xcode will welcome you will following exception:
setValue:forUndefinedKey: this class is not key value coding-compliant for the key
6) In your MyCustomView.m, paste / overwrite following code:
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (self)
{
NSString * nibName = #"MyCustomView";
[[[NSBundle mainBundle] loadNibNamed:nibName owner:self options:nil] firstObject];
[self addSubview:self.labelContinentName];
}
return self;
}
This step is crucial - it sets your outlet values (label1, button1) from nil to tangible subviews, and most importantly, sets their frame according to what you have set within MyCustomView.xib.
7) In your storyboard file, add view of type MyCustomView - just like any other view:
Drag a UIView in your View Controller main view rectangle
Select the newly added view
In Utilities -> Identity Inspector, set custom class value = MyCustomView.
It should be up & running no problem!
loadNibNamed does not handle frame setting, it only loads content and makes the objecet available to your code. initWithFrame: must be called to insert a new object to the view heirarchy of a window.
I have a custom UIView which was build with xib that have a file owner called CustomModuleUIView which contains labels and buttons . I have called this custom view in my Rootviewcontroller and I succeeded to display it using initWithCoder method. The problem is that I can't change the default text of UILabel neither from customMouleUIView nor from the root ViewController. I found example that tells me to do custom initialisation in in initWithCoder but it doesn't work for me and nothing changes and without any error it displays the default text.
This is my custom UIView xib
This is my root view controller
This is my code oh custom UIView .h
#import <UIKit/UIKit.h>
#interface ModuleCustomUIView : UIView
-(void) setup;
#property (weak, nonatomic) IBOutlet UIImageView *_moduleIcon;
#property (retain, nonatomic) IBOutlet UILabel *_moduleName;
- (IBAction)openDemo:(id)sender;
- (IBAction)close:(id)sender;
#property (weak, nonatomic) IBOutlet UIImageView *_moduleImage;
#property (strong, nonatomic) IBOutlet UILabel *_positionLabel;
#end
code of .m , i use setup method to init my UIView because I couldn't call
[[NSBundle mainBundle] loadNibNamed:xib owner:self options:nil] ;
inside initWithCoder that causes infinite loop .
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self)
{
}
return self;
}
-(void) setup
{
NSString *xib= #"CustomUIView";
NSArray *array=[[NSBundle mainBundle] loadNibNamed:xib owner:self options:nil] ;
UIView *view =[array objectAtIndex:0];
//code that doesn't work
[_positionLabel setText:#"hello world"];
//
[self addSubview:view];
}
this is my root view controller .h
- (void)viewDidLoad
{
[super viewDidLoad];
[_moduleCustomView setup];
[self.view addSubview:_moduleCustomView];
//code doesn't work
[_moduleCustomView setText:#"hello world"];
}
even in the did load I can't change the text
I have found my mistake , it's about file owner , i have change it in the inspector but by selecting the uiview and not te file's owner , i change NSObject to my class name and reconnect the label .
I guess the Files Owner attribute should be your 'root viewcontroller'.