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.
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
The problem is View is loaded but the inner elements are not loaded or did not appear why?
the xib image is:
and the corresponding .h is:
#import <UIKit/UIKit.h>
#interface DownloadView : UIView
#property (strong, nonatomic) IBOutlet UIView *view;
#property (weak, nonatomic) IBOutlet UILabel *downloadFilename;
#property (weak, nonatomic) IBOutlet UILabel *downloadpercentage;
#property (weak, nonatomic) IBOutlet UIProgressView *downloadprogressBar;
#property (weak, nonatomic) IBOutlet UIButton *downloadCancel;
#end
.m file is:
#import "DownloadView.h"
#implementation DownloadView
-(id)initWithFrame:(CGRect)frame{
self = [super initWithFrame:frame];
if(self){
[[NSBundle mainBundle] loadNibNamed:#"DownloadView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
-(id) initWithCoder:(NSCoder *)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
[[NSBundle mainBundle] loadNibNamed:#"DownloadView" owner:self options:nil];
[self addSubview:self.view];
}
return self;
}
The calling file is:
polygonView = [[DownloadView alloc]initWithFrame:CGRectMake(0, height-170, width, 100)];
polygonView.downloadFilename.text = #"Initiating Download...";
[polygonView.downloadprogressBar setProgress:0];
polygonView.downloadpercentage.text = #"0%";
[window addSubview:polygonView];
What is the Problem ?
Debugger update:
The output is:
Unfortunately you can't create custom UIView like this, it will not work. I use different way for reusable custom components which is:
Create new view controller in the storyboard, change its frame, and customize it.
Give it an identifier
In all places you want to use in storyboard, just create Container view and embed your custom view controller.
If you want to use it programmatically. Just instantiate it from storyboard with the identifier. Resize its view frame and add it as a subview.
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'.
I am quite new to iOS development and thus new to the concept of storyboard as well.
As this seems to be the 'new thing', everyone should use, I thought I might give it a try as well.
I got a project here, created with a Foo.xib file.
The xib file has several view objects included.
Then I have a class Foo.h and Foo.m class with following content:
Foo.h
#import <UIKit/UIKit.h>
#interface Foo : UIView
#property (strong, nonatomic) IBOutlet UIView *view01;
#property (strong, nonatomic) IBOutlet UIView *view02;
#property (strong, nonatomic) IBOutlet UIView *view03;
#property (strong, nonatomic) IBOutlet UIView *view04;
- (NSUInteger)viewCount;
#end
Foo.m
#import "Foo.h"
#interface Foo()
#property (nonatomic, strong) NSArray *views;
#end
#implementation Foo
- (id)init {
self = [super init];
if (self) {
self.views = [[NSBundle mainBundle] loadNibNamed:#"Foo" owner:self options:nil];
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
}
return self;
}
- (NSUInteger)viewCount {
return [self.views count];
}
#end
In my ViewController I would then load all the views and make it scrollable, like this:
#import "ViewController.h"
#import "Foo.h"
#interface ViewController ()
#property (nonatomic, strong) UIScrollView *scrollView;
#property (nonatomic, strong) Foo *views;
#end
#implementation ViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.views = [[Foo alloc] init];
CGSize fooSize = self.views.view01.bounds.size;
NSUInteger viewCount = [self.views viewCount];
self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, fooSize.width, fooSize.height)];
[self.scrollView setContentSize:CGSizeMake(viewCount*fooSize.width, fooSize.height)];
[self.scrollView setBounces:YES];
[self.scrollView setPagingEnabled:YES];
self.scrollView.delegate = self;
NSArray *views = #[ self.views.view01,
self.views.view02,
self.views.view03,
self.views.view04
];
for (int i=0; i<viewCount; i++) {
UIView *curView = views[i];
CGRect frame = curView.frame;
frame.origin.x = i*fooSize.width;
frame.origin.y = 0;
curView.frame = frame;
[self.scrollView addSubview:curView];
}
[self.view addSubview:self.scrollView];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
However, I have no clue, how to realize this with a storyboard. It seems to me that I have to have a NavigationController which is then linked to the Master View Controller. And now I would have to add a new ViewController for each view? Or is there a way to include all views within one ViewController like I did 'the old way'?
There is a massive mis conception that when using a storyboard it limits you to what you can do. A storyboard is simply like an array of .xib files, it holds many screens in the one file so you can see the entire flow of you app in one place. Inside a storyboard you can create a single viewController and assign a custom viewController class to it and then load / modify what ever you like inside the code of this viewController, as you have done above.
However the benefit of using the storyboard is to have multiple viewController objects so you can design all the screens and navigation there were you can see it, aiding you in debugging and design work.
If you already have the app working without a storyboard and you simply want to use it because its new but keep the old style of coding, you are not going to see much of the benefits. I would suggest following this example from the developer library on how to use a storyboard properly. After you complete this you will see the benefits of the new style and you can decide whether to do it in code or using the interface builder:
http://developer.apple.com/library/ios/#documentation/iPhone/Conceptual/SecondiOSAppTutorial/Introduction/Introduction.html#//apple_ref/doc/uid/TP40011318