Add Subview to UITableView's Parent view - iphone

I'm trying to add a subview to the parent view of a UITableView in order to have a subview that does not move when the UITableView is scrolled.
When I add the subview to the UITableView it moves with the table on user scrolling action.
So far, my scenario is that I have 3 UINavigationControllers on a UITabBarController.
Each has a UITableViewController.
I would like to add a subview to the screen of the individual controllers. However, I don't want the new subview to be added to the entire application like this:
[[[UIApplication sharedApplication] keyWindow] addSubview:newView];
When I push a new controller to the stack or pop one, I would like the subview to move and disappear with the UITableView it's on top of. Keeping the fact that when the user scrolls the UITableView, the new subview sits at its position without moving.
So that the UITableView and the new subview are on the same level.
Thanks

A couple of approaches:
First, to add a view to a table view that stays when the user scrolls, if your table only has one section, you can add a header for that section. For example, this adds a plain blue UIView as a header to the tableview:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, self.view.bounds.size.width, 50)];
view.backgroundColor = [UIColor blueColor];
return view;
}
- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
{
return 50;
}
Second, if you have more than one section, though, that trick doesn't work, and you'll want to use a UIViewController instead of a UITableViewController, add you header to the top of that controller's view and add a tableview to the bottom of that view. You'll want to make sure you set up (a) an IBOutlet for the tableview (generally to a property called tableView, to avoid confusion); and (b) specify the view controller as this tableview's data source and delegate.
Third, if you want this header to appear on every scene in your app, another approach (in iOS5+ apps) is to use view controller containment, create a container view that has your header, and then add your app's first scene as a child view of that view container. It's not hard, but if you're a new developer, I might discourage you from trying that. I've done it and it works well, but it's not for all people.
Fourth, if your app is using a navigation controller, you could customize the navigation bar to render your custom look and feel. It all depends upon what you're trying to accomplish with the header.

Add your subview to the tableview..
Implement the delegate method scrollViewDidScroll.
On scrollViewDidScroll, just set the required center of the subview you want.

Related

UITableViewController and sub views

I have a screen where I want to display the details of some product. I'm using storyboards.
I have done this using a tableview with static cells, but static cells can only be done within a tableview of UITableViewController. And the problematic point is that I also want to have a Imageview within this controller. this is not possible as the tableview of a UITableViewController take all the screen size.
So I'm doing the Imageview as a subview of the tableview.
I'm wondering if it is the right way to do it, there are similar issues on stackoverflow but none is corresponding to my use case (storyboard + tableviewcontroller + staticcell + sub view)
Without writing any custom code, you could either:
Put a UIImageView in the table view's header or footer view.
Create a static cell and put the UIImageView in that. Table cells don't have to be uniform length, you could make it taller than the other cells if needed.
You can have your subclass of UITableViewController. After you initialize it take its view (tableView) and add it as subview to some other view controller. This way you can set the frame of tableView to occupy the bottom of the (top containing) view controller. To the same view controller you can add additional subviews like UIImageView and position it on the top. The only problems you will encounter might be related to missing notifications to your UITableViewController subclass (viewWillDisappear, viewDidDissappear, viewWillAppear, viewDidAppear, ...). But you can forward them to from your top view controller. Or you could use methods from iOS 5 to add subclass of UITableViewController as a child view controller and position it as you wish. But this is useful if you plan on supporting devices with iOS 5.0+.
See documentation of method in UIViewController:
- (void)addChildViewController:(UIViewController *)childController __OSX_AVAILABLE_STARTING(__MAC_NA,__IPHONE_5_0);
Use a UITableView as a property of the viewController and add it to vc.view, rather than subclass a UITableViewController, then set its frame as you like
Add the imageView to vc. If you need to put it on top of the UITableView, for example, add it as the TableView's header.

Add tableview as subview (if you have to inherit from UITableVC)

What I want is a tableview with an ad view on top for admob and pull to refresh. Right now i'm using the tableheaderview, but that scrolls and I need the ad to persist. viewforheader doesn't scroll, but gets rid of the standard headers that i need for the section headers. if im inheriting from a tableVC, is there anyway to override loadview to build a static view to hold an ad and then have the tableview below that? i've tried writing loadview and can get the ad, but when trying the tableview, i get "unable to restore selected frame" in the console. i'm using culver's pull to refresh technique as its very simple to implement. i know a tableVC assumes the root view is a tableview so how can i get around that? every solution on the net says use a standard uiviewcontroller, but im stuck cause of the pull to refresh
this is in my loadview:
self.tableView = [[UITableView alloc] initWithFrame:CGRectMake(0, 60, 320, 400) style:UITableViewStylePlain];
self.tableView.dataSource = self;
self.tableView.delegate = self;
[self.view addSubview:self.tableView];
Make your view controller inherit from UIViewController, but continue to implement <UITableViewDataSource, UITableViewDelegate>. Add your TableView as a sub-view of the UIViewController's built-in view, pointing to File's Owner for data source and delegate. Make the table view less than 100% of the height, and have the ad as another view within the main view that makes it appear below/above the table.
source: I do this in my apps, and they are on the store.
There are lots of things you could try.
You could stop inheriting from UITableViewController, like everyone says. If you're using Culver's PullRefreshTableViewController, adapt it to just subclass UIViewController. You'll need to add back the tableView property, and adopt the UITableViewDataSource and UITableViewDelegate protocols if you do.
You could set the contentInset of the table view to leave room at the top, and define scrollViewDidScroll: in your controller to reposition the ad view appropriately on each scroll. (UITableView subclasses UIScrollView, so it will call the UIScrollViewDelegate methods if you define them.)

Absolute positioning over tableView, view hierarchy and redrawing

on the main screen on my project I have a a TableView, with a navigationBar on top and toolBar on the bottom. The thing is, one of the buttons in the toolbar needs to slide up an UISegmentedControl from the toolbar. I have already implemented this control in a custom UIView, BUT, if i just add it to my rootviewcontroller.view and slide it into place, it will scroll with the rest of the table, which is undesired (I would like it to appear as an extension of the toolbar).
So, what do I do? In rootViewController I do
self.filterView = [[FilterView alloc] initWithTarget:self.tableView reloadDataSelector:#selector(reloadData)];
[[self.view superview] addSubview:self.filterView];
[[self.view superview] bringSubviewToFront:self.filterView];
I add the control view (self.filterView) to my view's superview, and that puts it above the tableview's scroll.
BUT, now the problem. As soon as the tableView goes out of view (I push another view on the navigationController, or specially if the app goes to background) this view gets re layed-out, and my controller view gets moved to (0,0).
The thing is, as far as pushing new views on the navigationController, I can kind of control it by repositioning it in viewWillAppear and viewWillDisappear in my rootViewController. But when the app goes to background and comes back those functions don't get called.
So, is there any way to
(a)prevent my controller view from being moved
(b)detecting when it has been moved unintentionally
(c)detecting coming and going from background from rootViewController
???
I know I can detect passes to background in appDelegate, but I wouldn't feel comfortable dealing with layout issues there.
Thanks!!
EDIT:
To add some info, if I do
NSLog(#"%#",[self.view superview]);
//I get <UIViewControllerWrapperView: 0x61589d0; frame = (0 64; 320 372); autoresize = W+H; layer = <CALayer: 0x615d9c0>>
EDIT2: I guess I could create my own wrapper UIView first, load all my current view hierarchy in it, and put the controller view there. Do you guys think that would be worth the trouble? isn't there a simpler way?
EDIT3: Ended up opting for changing my rootViewController from UITableViewController to UIViewController, and added the tableView programatically, as Phil suggested below. I can now control my main view as I like, and since I am putting my segmentedControl view there I can control how it is positioned, as opposed to before, when I was placing it in an UIViewControllerWrapperView, which I am not too sure who controls or what it does to it's subviews.
SO, just out of curiosity, does anyone know why the UIViewControllerWrapperView that was wrapping my UITableViewController's view was moving my UIView on coming back from background??
To clarify, the setup was like so:
UIViewControllerWrapperView
|
|UITableView
|Custom SegmentedControl UIView
As a side note, you have a pattern in your code that looks like that view is going to leak and addSubview will automatically put the view on the top of the view order.
However, the reason your view is scrolling is because it is being added as a subview of the UITableView, which is a subclass of UIScrollView. When the scroll view scrolls, it will move any subviews up or down by the contentOffset property. As the UIScrollView scrolls it will repeatedly layout its subviews. Since the table view isn't aware of your custom subview, it appears it is just moving it to the 0,0.
I assume you are using UITableViewController. If you plan to have more than just a table view for this view controller, then you should implement a standard view controller instead. This controller would have a normal view that contains the tableview and your other views. UITableViewController is merely for convenience for a very common case.
It's very easy to duplicate UITableViewController's functionality if you are worried about that. It is actually very clearly documented.
When the table view is about to appear
the first time it’s loaded, the
table-view controller reloads the
table view’s data. It also clears its
selection (with or without animation,
depending on the request) every time
the table view is displayed. The
UITableViewController class implements
this in the superclass method
viewWillAppear:. You can disable this
behavior by changing the value in the
clearsSelectionOnViewWillAppear
property.
In your implementation:
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([self.tableView numberOfSections] == 0) {
[self.tableView reloadData];
} else {
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated];
}
}
When the table view has appeared, the
controller flashes the table view’s
scroll indicators. The
UITableViewController class implements
this in the superclass method
viewDidAppear:.
In your implementation:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.tableView flashScrollIndicators];
}
It implements the superclass method
setEditing:animated: so that if a user
taps an Edit|Done button in the
navigation bar, the controller toggles
the edit mode of the table.
In your implementation:
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
[super setEditing:editing animated:animated];
[self.tableView setEditing:editing animated:animated];
}

Adding UIView subview to single table view controller in navigation stack

I'm working on an app that has three table view controllers in a navigation stack. The root view controller and the second VC have toolbars, but I want to add a subview to the second view controller like this. (The color is just there for visualization.)
I want to add the view programmatically, since I haven't been able to do it with IB without major headaches. Right now, I've been able to kind of get what I want by drawing a UIView in the second view controller like this:
- (void)viewDidLoad {
[super viewDidLoad]
UIView *detailView = [[UIView alloc] initWithFrame:CGRectMake(0, 392, 320, 44)];
detailView = [UIColor redColor];
[self.navigationController.view addSubview:detailView];
[detailView release];
}
The problem with this approach is that once the UIView is loaded in the second view controller, it stays loaded and is drawn in the third and root view controllers. I've tried a variety of methods of removing the UIView, including setting the detailView to nil in viewDidUnload, calling removeFromSuperview in didSelectRowAtIndexPath (which removed the view from the whole stack).
I've also tried adding the subview to self.view, but that pushes it below the visible area of the table view, so I have to scroll up to see it, and it snaps back down when I let go.
Clearly, adding this subview to the navigation controller is not the best way to do what I want, but I'm at a loss as to where to go from here.
As you've already discovered, you definitely should not be reaching up into the navigation controller's view.
You want your SecondViewController to be an UIViewController that implements the UITableViewDelegate and UITableViewDataSource and whose view lays out the UITableView and the UIView you wish to use for your stationary 'footer' in it's own main UIView.
It helps to keep in mind that UITableViewController is ultimately is just a convenience for creating a view controller whose view consists entirely of a UITableView.
Anyway, rather than attempt to put a pile of that code inline in this answer, you can browse it (or svn co) from this read-only svn repo.
EDITED (now that it's not midnight, putting some code/explanation directly in answer):
For the controller to be pushed onto the nav stack that needs the footer create a new UIViewController-based class (do NOT check the 'UITableViewController subclass' box in the template selection dialog).
Add instance variables for the UITableView and the UIView that is to be the extra bottom view.
#interface SecondViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
UITableView* tableView;
UIView* customFooterView;
}
#property (nonatomic,retain) IBOutlet UITableView* tableView;
#property (nonatomic,retain) IBOutlet UIView* customFooterView;
#end
In IB add a UITableView and UIView to the existing root view for the controller and lay them out as desired (probably worth altering the auto-resize parameters too if your app can be used in both landscape and portrait). Hook up the two views to the outlets defined for them in the "File's Owner" and also ensure you hook up the UITableView's delegate and dataSource properties to point at the "File's Owner."
Then just implement the UITableViewDelegate and UITableViewDataSource protocols as appropriate for your application.
If you want to lay out the entire 'footer' view in IB then go right ahead. Otherwise you can easily add items programmatically in viewDidLoad (and remember to tear it down in viewDidUnload).
I don't like the approach. You should put your table view inside another view, and put your detail view together in that view.
Despite of that, I think you can remove your view in viewWillDisappear method of your view controller. I also notice that you did not keep your detailView as a private variable, which you should do because you need to reference it when removing it later (I still wonder how you have done it.)
Note that viewDidUnload is called in case of view unloading (i.e. releasing from its controller), so it is not related to navigation.
Not sure which behavior you're looking for but try one of these:
Assign the detailView to the tableFooterView property of the tableview on the second VC.
Reduce the height of the table view and add the detailView to self.view.

How do I reduce the height of a TableView when it is constructed in IB?

I wanted to add a view to the bottom of my screen. The controller is a UITableViewController, how do I shrink the tableView and add a extra view at the bottom of the tableview?
I've tried setting the frame of self.tableView in different places (viewDidLoad, viewWillAppear etc) but nothing happens. The tableView is created by IB and not programtically.
I've tried added a footer to my table view but that's not what I want, because the footer actually scrolls up, I want a static non moving View at the bottom of the screen.
I'm not saying you can't do it otherwise, but you may not want a UITableViewController for this situation. You can still have your view controller implement UITableViewDelegate and UITableViewDataSource, but place a vanilla UIView in your nib, into which you place a UITableView. Then just make sure to set the view outlet to the UIView containing your table. This has the effect of allowing you to create your additional view within IB. I just tried this and it appeared to work.
I'm guessing you're using a UINavigationController. When you push a controller onto your navigation stack, UINavigationController resizes its view to full screen, ignoring the geometry and autoresizing behavior you've defined in IB.
This resizing seems to happen after viewWillAppear:. In the past I've had some success resizing a table view and adding a sibling view in viewDidAppear:, after calling [super viewDidAppear:]. This is a bit risky though, since Apple could break it by changing how UINavigationController works behind the scenes.
A safer option is to push a view controller onto your navigation stack that controls a wrapper view. Then add your UITableView and its sibling as subviews of that wrapper view. The annoying thing about this option is that you'll probably want to use a nested UITableViewController to manage your non-full screen table view, but the documentation for UIViewController says it's designed to manage full screen views only. If you decide to ignore this admonition and nest your view controllers anyway, you'll find that viewWill/DidAppear/Disappear don't get called on the nested controller, so you'll have to manually delegate those methods from your wrapper view controller. This lack of support for nested controllers is one of my biggest pet peeves about UIKit, and I've gone to great lengths to engineer around it.
If you want to toe the line and use view controllers only for full screen views, you can push a normal view controller that controls your full screen wrapper view, manually implement all the UITableViewDataSource and UITableViewDelegate methods in your view controller, and set it as the delegate for your table view.
you want to change the -loadView method. Not viewDidLoad or viewWillAppear. This will allow you to make additional configurations with your tableview even if it is created in IB.
- (void)loadView {
[super loadView];
CGRect titleRect = CGRectMake(0, 0, 300, 40);
UILabel *tableTitle = [[UILabel alloc] initWithFrame:titleRect];
tableTitle.textColor = [UIColor blueColor];
tableTitle.backgroundColor = [self.tableView backgroundColor];
tableTitle.opaque = YES;
tableTitle.font = [UIFont boldSystemFontOfSize:18];
tableTitle.text = [curTrail objectForKey:#"Name"];
self.tableView.tableHeaderView = tableTitle;
[self.tableView reloadData];
[tableTitle release];
}
I don't know how to do it in IB but the way to do it in code is with this:
- (void) loadView
{
UITableView *tv = [[UITableView alloc] initWithFrame: rect
style: UITableViewStyleGrouped];
// finishg configuring table view
self.view = tv;
[tv release];
}
Trying to do it in two stages -- style first and then frame or frame first and then style -- neither of them works.