xib-specified location moves on UIView load? - iphone

I have a couple of view controllers that I want all to share a header status bar. This header view shows status of certain things in my app. I used IB to lay out the contents of the header view in its own xib, then also used IB to layout the view controllers, adding a placeholder UIView element in the view controller's xibs for when I load in the header programmatically.
Now, when I create a UIView subclass from the contents of the view xib, and add that as a subview to the view of any of my view controllers, the location that I specified for the header view in my view controller xib seems to get ignored, and the header view gets placed at (0,0) no matter what. All the contents of the header view load and appear fine, it's just the location of the header view in the view controller that is wrong.
Here's how I'm loading and adding my header view to my view controller:
header = [InfoHeader loadFromNib:MY_NIB_NAME withOwner:self];
[self.view addSubview:header];
The view controller xib is confirmed to have an outlet connected to the 'header' variable. And here's that 'loadFromNib' method:
+(InfoHeader *) loadFromNib: (NSString *) nibName withOwner:(id) objectOwner
{
NSBundle *b = [NSBundle mainBundle];
NSArray *nib = [b loadNibNamed:nibName owner:objectOwner options:nil];
InfoHeader *header;
for (id obj in nib)
{
if ([obj isKindOfClass:[InfoHeader class]])
{
header = (InfoHeader *) obj;
}
}
return header;
}
Any ideas on what to check here? I'd rather not locate the header bar programmatically, but let IB do it.

Did you change the location of your subview in IB? Usually, the default location (0, 0) of the view will be the (top, left) coordinate of the big view in the screen. I think checking that will work fine

You can do this without using a "placeholder" view at all, by programmatically specifying the frame of the subview:
InfoHeader *header = [InfoHeader loadFromNib:MY_NIB_NAME withOwner:self];
header.frame = CGRectMake(0, 44, header.frame.size.width, header.frame.size.height);
[self.view addSubview:header];
(that code asssumes that you have removed the header outlet from your class, so it can be a local variable instead).
Or, if you'd rather use the placeholder (so you can specify the header's position using IB instead of programmatically), do it like this:
InfoHeader *headerView = [InfoHeader loadFromNib:MY_NIB_NAME withOwner:self];
[header addSubview:headerView];

Related

EXC_BAD_ACCESS (code=2 or code 1) when call button action

I am using the iCarousel Library and am having some problems.
In the Controls Demo Example Project, a XIB file is used and the view is setup like:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
if (!view)
{
//load new item view instance from nib
//control events are bound to view controller in nib file
//note that it is only safe to use the reusingView if we return the same nib for each
//item view, if different items have different contents, ignore the reusingView value
view = [[[NSBundle mainBundle] loadNibNamed:#"ItemView" owner:self options:nil] lastObject];
}
return view;
}
Because I am using Storyboards I create a View Controller and setup the view like this:
- (UIView *)carousel:(iCarousel *)carousel viewForItemAtIndex:(NSUInteger)index reusingView:(UIView *)view
{
NSString * storyboardName = #"MainStoryboard";
NSString * viewControllerID = #"DuuinNewsItem";
UIStoryboard * storyboard = [UIStoryboard storyboardWithName:storyboardName bundle:nil];
//create new view if no view is available for recycling
if (!view)
{
DUDuuin *tDuuin = [_duuins objectAtIndex:index];
DUNewsItemViewController * controller = (DUNewsItemViewController *)[storyboard instantiateViewControllerWithIdentifier:viewControllerID];
controller.duuin = tDuuin;
view = controller.view;
[view setFrame:CGRectMake(0, 0, 314.0f, 415.0f)];
}
return view;
}
When I add an action to any button in the view I get the error:
I have tried a lot of things that are recommended in Stackoverflow, but I cannot find a solution:
I tried:
Setting the outlet to Strong (someone says it's a problem because ARC)
Deleting the sender in the action
Adding the method in the View Controller that have the icarousel
****UPDATE****
Now i see other issue. When i define the Action in the DUNewsItemViewController and try it in the simulator, it says :
-[__NSCFType btnTest:]: unrecognized selector sent to instance 0x1577c650
So, i adde the method in the .m file of the View Controller that have the iCarousel and the problem still the same:
EXC_BAD_ACCESS (code=2)
Some info:
I'm using ARC
It's on Storyboards
The number of views is dynamic
Your main issue here is not with the view specifically but rather with your DUNewsItemViewController instance. You create your controller in the method you have provided and return the view. The view is being retained by iCarousel. Your DUNewsItemViewController on the other hand is not. There is nothing that is strongly pointing to it. Since there are no strong pointers to it, it is being deallocated. Your view is displaying correctly, with all of your buttons, because again, it is being retained outside of your DUNewsItemViewController. When a button is pressed it attempts to access its action method and fails because the controller no longer exists.
To fix this issue you need to create a strong pointer to your controller (not the button as you attempted before). I am not recommending a perfect strategy, rather one that works.
You could create a mutable array (as a property) and add the viewcontrollers into it (if there are more than one) as you create them. This way the controller that is iCarousel's delegate/datasource holds a reference to it:
if (!view)
{
DUDuuin *tDuuin = [_duuins objectAtIndex:index];
DUNewsItemViewController * controller = (DUNewsItemViewController *)[storyboard instantiateViewControllerWithIdentifier:viewControllerID];
// Add controller to array to hold a strong reference to it.
[self.myMutableArray addObject:controller];
//
controller.duuin = tDuuin;
view = controller.view;
[view setFrame:CGRectMake(0, 0, 314.0f, 415.0f)];
}
Otherwise, if there is only one (or a few) you could create unique outlets:
#property (strong, nonatomic) DUNewsItemViewController *firstVC;
and in your carousel:viewForItemAtIndex:reusingView:view method include a line self.firstVC = controller.
If something does not make sense let me know. Again, there are probably better implementations but this should work. The reason that the XIBs worked in the example is because they are just UIViews not UIViewController subclasses like you are using. Again, your view is working like it should (displaying, just like the example), but your controller is being deallocaed (not like the example, since there is no controller being used).

Adding ViewController as a subView in a UITableViewCell - Not showing up

I'm trying to add a ViewController to my TableView cell however it is not showing up when testing.
PhotoLocation is a UIViewController subclass. I have a View Controller created on the StoryBoard with a bunch of UILabels, UITextFields, UISwitch, etc configured on it. PhotoLocation is associated with that StoryBoard View Controller and its Identifier is PhotoLocation.
In a separate controller - UITableViewController, I have a working TableView with cells that are populating with various information, I am trying to add the PhotoLocation VC as a subview of each cell. (Note: PhotoLocation is a freeform sized VC that is of size 210x100).
In my cellForRowAtIndexPath method I'm doing the following:
// snip code above which sets up the cell and performs various other things
PhotoLocation *photoLocation_ = [self.storyboard instantiateViewControllerWithIdentifier:#"PhotoLocation"];
[photoLocation_.view setFrame:CGRectMake(115, 0, 210, 109)];
[cell.contentView addSubview:photoLocation_.view];
return cell;
When I run the app the subview does not show up in the cells. Is there something here I'm missing?
If I understand you correctly, try doing creating a nib file (File-->New File-->User Interface-->View). Delete the "view" and drag a Table View Cell out. Lay it out as you have described in your post. In your tableViewController do:
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:#"TVCell" owner:self options:nil];
cell = tvCell;
self.tvCell = nil;
}
The reason being, you cannot have 2 "viewControllers" on screen at the same time with the exception of Controllers of Controllers (Tab Bar, Navigation, SplitView). You are trying to put one view controller in another view controller right now.
Have you tried this?
PhotoLocation *photoLocation_ = [self.storyboardinstantiateViewControllerWithIdentifier:#"PhotoLocation"];
[photoLocation_.view setFrame:CGRectMake(115, 0, 210, 109)];
[cell addSubview:photoLocation_.view];
return cell;

Accessing UITabBarController's content view

Is there a way to access the content view (shown as Custom content on the diagram) in order to change its frame?
Access to selected view controller's view in tab bar
UIView *view = tabBarController.selectedViewController.view;
Access to second view controller's view in tab bar
UIView *view2 = [(UIViewController*)[tabBarController.viewControllers objectAtIndex:1] view];

ViewController with multiple views from nibs at a time

I am in the process of developing an IOS rpg. This game is a controlled by a tab bar, and every view controller in the tab bar will have a common "header" that sits at the top of the screen and shows information about the player.
The rest of the screen, however, will show one of many different views. Each view controller will be responsible for showing multiple different "views" underneath the "header" view. In addition, many of these views will need to be scrollable, as they will not fit in the confines of the screen.
Questions:
1)How do you add two views from separate nibs to a single view controller.
2)How do you embed only one of those views in a scroll view.
Thank you.
You can load a nib through the loadNibNamed:owner:options: function on a NSBundle. What it will return is an array of all the objects in the nib (the list you see on the left when you create a nib in interface builder). If you're view is the first item on the list of objects in the nib, then its the object at the 0th index of that array.
NSArray *objects1 = [[NSBundle mainBundle] loadNibNamed:#"View1Nib" owner:self options:nil];
UIView *customView1 = [objects1 objectAtIndex:0];
NSArray *objects2 = [[NSBundle mainBundle] loadNibNamed:#"View2Nib" owner:self options:nil];
UIView *customView2 = [objects2 objectAtIndex:0];
UIScrollView *scroll = [[[UIScrollView alloc] init] autorelease];
[scroll addSubview:customView2];
[[self view] addSubview:customView1];
[[self view] addSubview:scroll];
If I have multiple views in a nib I make use of the restoration identifiers rather than relying on the order of the array and perform the following:
UINib *nib = [UINib nibWithNibName:#"Nib" bundle:nil];
NSArray* views = [nib instantiateWithOwner:self options:nil];
assert(views.count == 3);
UIView *aView;
UIView *anotherView;
UIView *yetAnotherView;
for (UIView* view in views) {
if ([view.restorationIdentifier isEqualToString:#"AViewId"]) {
aView = (SettingsCell *) view;
}
else if([view.restorationIdentifier isEqualToString:#"AnotherViewId"]) {
anotherView = (SettingsCell *) view;
}
else if([view.restorationIdentifier isEqualToString:#"YetAnotherViewId"]) {
yetAnotherView = (HeaderView *)view;
}
}
assert(aView && anotherView && yetAnotherView);
When you make a view controller, if you choose to generate an xib automatically, its view outlet will, by default be connected to a view. Now, create a new xib, with some different name, and make its files owner as your view controller class. Also, manually connect the view outlet.
Now, call the init method:
YourViewController *x = [[YourViewController alloc] initWithNibName: #"yourNibName" bundle:nil];
according to whatever xib you want to load, place the name instead of "yourNibName". Hope that helps.
You'll need to have references to both views, and you can simply [view addSubview:secondView]; as normal. As for how you get a reference to the views in the xib that is not associated with your view controller, there are several ways, but which you choose will depend on whether that view is already instantiated elsewhere in the app. I'm betting you're already instantiating that view elsewhere, and you simply want to add it. If it were me, I would use a singleton for that view's parent, so I could do something like:
[view addSubview:[ParentClass parentClassSharedInstance] viewToAdd]];
Scroll views are a beast you'll need to work with to fully understand, but you add views to them just like any other view. The important bit is that they have a contentSize property that can be bigger than their frame's size. I usually use a single view of the size I want to manage all views underneath the ScrollView. Good luck!

Nib is automatically being added to the screen

I have a nib file I'm trying to instantiate in code. My UIViewController's main view is also loaded from a nib file.
Here's my UIViewController's viewDidLoad method:
- (void)viewDidLoad
{
[super viewDidLoad];
UINib *nib = [UINib nibWithNibName:#"MyCustomView" bundle:nil];
NSArray *nibViews = [nib instantiateWithOwner:self options:nil];
MyCustomView *myView = [nibViews objectAtIndex:0];
myView.frame = CGRectMake(100.0f, 100.0f, 91.0f, 91.0f);
[self.view addSubview:myView];
}
This creates some sort of endless loop. If I comment out [self.view addSubview:myView], myView appears, but everything currently on the screen disappears. I didn't think instantiateWithOwner added the view to the screen. If it does, how do I get access to it?
Thanks for your help.
The instantiateWithOwner method reassigns the properties of your view controller (set from the nib the controller was created from) to ones from the new nib. Those properties likely include the view property, so that method, within it, contains a call to setView:, and sets the view controller's view to the new nib's view. Afterwards, you're trying to add a view as a subview to itself, and that, naturally, causes problems.
You want to create your own property, for instance, secondaryView, set the nib's view to that, and add it as a subview. You don't want to reassign your view controller's view.