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;
Related
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!
So I have two tableViews, one is shown first in a popover, then when a row is selected, it brings up another tableView as the detailView. Then I make a selection and update my model on the detailTableView. Then when I go back to the original tableView, my data for that row is not updated.
I have a UINavigationController as the contentViewController for this popover controller. I didn't know how I could update the label for that cell when I pop off the detailTableView. TIA.
Edit: Essentially, I'm trying to do something similar to settings->Locations. When you click on Locations, it brings you up to a detail View. If you turn Locations off, then your main view says Locations -> Off. If Locations is turned On, then it would say Locations->On on the main Table View. I don't know how to get a reference to the row that gets created in cellForRowAtIndexPath to change the label for that row only if that makes sense. Thanks.
Edit: Added code below
So I understand that I can send a notification, but I don't know how to update that specific cell when I go back to the mainTableView since I don't have a reference to the cell. Thanks.
In my cellForRowAtIndexPath:
else if ([indexPath section] == VERSION) {
cell = [tableView dequeueReusableCellWithIdentifier:#"VersionCell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:#"VersionCell"] autorelease];
}
NSArray *array = [[dmgr VersionDictionary] allKeysForObject:[NSNumber numberWithBool:YES]];
cell.textLabel.text = [array objectAtIndex:0];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
You can either tie two TableView controllers together by passing the popover tableVC into the detail, so the detail can notify the popover that data has changed,
or
you can have the popover tvc register for a notification, and have the detail tvc post that notification. When popover receives it, it reloads,
or
you can have popover tvc be a KVO listener to the actual model data that changes, and when it does, reload.
why dont you just send a delegate from detail view and update the first table view
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.
I want to add a uiviewcontroller's view which has a button and few labels as a content view of a uitableviewcell.
Though i am able to add but the button action causes a crash.
MyViewController *controller = [[MyViewController alloc] initwithnibname:#"MyView" bundle:nil];
[cell.contentview addsubview:controller.view];
[controller release]; // if i comment out this part the button action is received by my view controller.
However there are memory leaks when its removed from view. The dealloc of myviewcontroller is not called.
What is the correct way to do this?
Add a view to a uitableview cell
which has a button and is handled by
the viewcontroller
How to assure memory is released
when the view goes out of scope?
TIA,
Praveen
I think the problem is, that you are releasing the controller and just using the subview which is retained by its superview. The action pattern needs a target which I assume is the released controller. And why should you release your viewController if you only need the view of it? Retain it and keep a reference through a property to it.
My way of adding subviews to a tableview cell would be in a subclass of UITableViewCell. Let's assume you are having a subclass of UITableViewCell, say ButtonTableViewCell. The init of the of cell creates and adds a UIButton to your cell and puts it nicely in its contentView. Decalre a property which references to the button. Like UIButton *myButton. What should be done in the cellForRowAtIndexPath is something like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
ButtonTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyButtonCell"];
if (cell == nil) {
cell = [[ButtonTableViewCell alloc] initWithReuseIdentifier:#"MyButtonCell"];
}
[cell.myButton addTarget:self action:#selector(onDoSomething) forControlEvents:UIControlEventTouchUpInside];
// Do more cell configuration...
return cell;
}
I've made up the initializer initWithReuseIdentifier which can be easily implemented.
You assure release of memory with the viewDidUnload method of the UIViewController and the dealloc method.
"button action causes a crash" - What is the crash when you tap the button?
Also, you only appear to be using the view of MyViewController (since you add the view to the cell and then release the controller)- what is this controller supposed to do other than contain a view? Why not just use a view?
Also, (wild guess here) the usual constructor of a button does not have new/alloc/copy, and therefore does not warrant a release. I've seen a lot of code crash from inappropriately releasing UIButton's.
Adding a view of a controller as a subview to any other view does not retain the controller.
The controller is released immediately after the release call and any button actions will be sent to deallocated instance.
We can avoid this by maintaining a strong reference to the controller
#Property(nonatomic,strong)MyViewController *controller;
or by adding a view controller as the ChildViewController
[self addChildViewController:controller];
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];