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.
Related
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 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.
I've managed to setup a custom UIView class with a nib.
My .h looks like
#interface MyView : UIView <UITextFieldDelegate>
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) MyView *topView;
And .m
#implementation MyView
NSString *_detail;
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
MyView *v = [[[NSBundle mainBundle] loadNibNamed:#"MyView" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if (self.topView == nil)self.topView = self;
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
-(NSString *)topDetail{
return _detail;
}
-(NSString *)detail{
return [self.topView topDetail];
}
-(void)setTopDetail:(NSString *)detail{
_detail = detail;
}
-(void)setDetail:(NSString *)detail{
[self.topView setTopDetail:detail];
}
- (BOOL)textFieldShouldReturn{
//here I show an UIAlertView using self.detail for the message
}
Note: The setup I have works exactly how I want it to.
The problem
What I would like to do is remove my manual detail methods and turn NSString *_detail into #property (...)NSString *detail
When I try it with the #property, then within my ViewController if i call
myView.detail = someString, myView will be referring to the top most view. Then if textFieldShouldReturn gets called because of user interaction, then it calls the nested MyViews _detail which has not been set.
What I want:
To not have to write extra code for access to _detail regardless of where I'm accessing it from. I want to merely declare the property and go on with my usual coding.
Your problem is that you're trying to keep the a class reference, topView, with an object property.
In other words every objects' topView is the object itself, which makes no sense.
Your definition should be:
#interface MyView : UIView <UITextFieldDelegate>
// Class "properties"
+ (instancetype)topview;
+ (void)setTopView:(UIView *)topView;
// Object properties
#property (nonatomic, weak) IBOutlet UITextField *textField;
#property (nonatomic, strong) NSString *detail;
Now you can keep track of the topView:
static MyView * _topView;
#implementation MyView
+ (instancetype)topView {return _topView}; // You could also create one here lazily
+ (void)setTopView:(UIView *)topView { _topView = topView };
-(id)initWithCoder:(NSCoder *)aDecoder{
if ((self = [super initWithCoder:aDecoder])&&self.subviews.count==0){
JUITextFieldHint *v = [[[NSBundle mainBundle] loadNibNamed:#"JUITextFieldHint" owner:self options:nil] objectAtIndex:0];
self.textField = v.textField;
if ([MyView topView] == nil)[MyView setTopView:self];
v.topView = self.topView;
[self addSubview:v];
}
return self;
}
No more need for manual setters and getters. Now you can use your detail property, either with anyInstance.detail or [MyView topView].detail, or even MyView.topView.detail if you like dots like me ;)
You're init method still looks weird but should work. Check Apples init template.
Lastly, textField can be weak as long as it has a superview, otherwise make it strong.
My xib contained one UIView (no controller). I had the UIView set to MyView for the class.
I changed the UIView back to just UIView then set File's Owner to MyView. This solved issues of recursion (which is why I had such a weird setup in the first place) and caused my variables and IBOutlets to be linked up properly.
Credit goes to How do I create a custom iOS view class and instantiate multiple copies of it (in IB)? and some of the comments which I missed the first couple times I read through it.
I have a UIViewController which I placed UISegmenetedControl with 2 options and beneath I have a UIView which acts as a container for putting my custom UIView (that is actually a UITableView). When switching between segments I would like to switch between 2 different UITableViews.
My problem is with the UITableView.
I have created a custom UIView class with .xib and inside I put a UITableView and I'm able to populate the data into the table and see it correctly.
The problem is with the scrolling, it doesn't react to vertical scrolling at all!
Here is how I created the UIView with its table.
.h file
#interface LeaderboardTableView : UIView
#property (strong, nonatomic) IBOutlet UIView *view;
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (strong, nonatomic) NSDictionary *myScore;
#property (strong, nonatomic) NSArray *players;
- (id)initWithBoardType:(LeaderboardType)boardType myScore:(NSDictionary*)myScore leaderboardData:(NSArray*)data;
#end
.m file
#implementation LeaderboardTableView
- (id)initWithBoardType:(LeaderboardType)boardType myScore:(NSDictionary*)myScore leaderboardData:(NSArray*)data {
self = [super init];
if(self) {
_players = data;
_myScore = myScore;
_boardType = boardType;
self.tableView.delegate = self;
self.tableView.dataSource = self;
}
return self;
}
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if(self) {
[self setup];
}
return self;
}
- (void)setup {
[[NSBundle mainBundle] loadNibNamed:#"LeaderboardTableView" owner:self options:nil];
[self addSubview:self.view];
}
Here is my .XIB
What am I doing wrong?? I suspect that my UITableView resides in UIView and that's why I can't scroll but I cannot figure out how to solve this.
Thank you!
Assuming that you use initWithBoardType:myScore:leaderboardData: to instantiate your view, try to change :
self = [super init];
by
self = [self initWithNibName:#"LeaderboardTableView" bundle:[NSBundle mainBundle]];
in this method.
But it's not sure that it will fix your scrolling problem. It looks like if there were a "invisible" view over your table. Let me know how you display your view.
A better idea would be to use one table view and switch out the data source for each different UISegmentedControl tap.
Finally I was able to resolve this problem with a lot of help from zbMax !
Eventually I made my custom Table to subclass UITableViewController with XIB. I implemented all the logic of populating cells and embedded this TableView in my parent view controller, this way I could switch between 2 Views of tables.
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'.