I've got a UITableView that I'd like to stick a 44px subview on top of. I tried tableViewHeader, but that scrolls with the rest of the table.
I tried searching and have found many people saying I need to add a UIView superview and then add my header and the UITableView to it. However I can't find an example on exactly how to do this. I tried making a new UIView subclass and laying out the subviews in IB, but I ran into trouble getting the table controller to link w/ the UITable (because I don't know enough about IB).
How can I do this with XIBs? Can someone provide an example?
Thanks for any help you guys can provide.
I finally figured this out right after posting. Figures. :)
Here's what I did, in case others run into the same problem:
Delete the existing UITableViewController and its XIB. They're junk. Get really mad while you do.
Make a new UIViewController subclass with a XIB
Open XIB in IB and add your header stuff and a UITableView to the UIView
In the IB Outlets for UITableView make sure you connect Delegate and DataSource to your File Owner
In the header for your view controller, be sure to add <UITableViewDelegate, UITableViewDataSource> to implement these protocols
Implement all the regular UITableView delegate and data source methods you know and love, but in your UIViewController instead of the way you're used to doing it through UITableViewController
After this things should work.
The problem is, UITableViewController's view property is the same thing as the tableView property. I had the same problem, wanting to put some fixed content above the table. I didn't want to change the base class, as it provides lots of great functionality I didn't want to lose or disrupt.
The fix is actually easy. The trick is to create custom set and get for self.tableView property. Then, in loadView, you replace the view with a fresh UIView and add the tableView to it. Then you're free to add subviews around the tableView. Here's how it's done:
In header:
#interface CustomTableViewController : UITableViewController
{
UITableView *tableView;
}
In .m:
- (UITableView*)tableView
{
return tableView;
}
- (void)setTableView:(UITableView *)newTableView
{
if ( newTableView != tableView )
{
[tableView release];
tableView = [newTableView retain];
}
}
- (void)loadView {
[super loadView];
//save current tableview, then replace view with a regular uiview
self.tableView = (UITableView*)self.view;
UIView *replacementView = [[UIView alloc] initWithFrame:self.tableView.frame];
self.view = replacementView;
[replacementView release];
[self.view addSubview:self.tableView];
//code below adds some custom stuff above the table
UIView *customHeader = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 20)];
customHeader.backgroundColor = [UIColor redColor];
[self.view addSubview:customHeader];
[customHeader release];
self.tableView.frame = CGRectMake(0, customHeader.frame.size.height, self.view.frame.size.width, self.view.frame.size.height - customHeader.frame.size.height);
}
Enjoy!
Replace the TableViewController with a ViewController, inside it add a UIView with fixed height to place the fixed content you need, and below that add a UITableView.
Create an outlet to your TableView
#IBOutlet weak var tableView: UITableView!
You can reuse all the funcs you already have removing the override word for example
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let c = tableView.dequeueReusableCellWithIdentifier(cellId) as UITableViewCell!
c.textLabel?.text = "A title"
c.detailTextLabel?.text = "A subtitle"
return c
}
See this answer to add the automatic refresh control if you need it Pull to refresh UITableView without UITableViewController
As an option it is possible to embed UITableViewController as part of UI into another UIViewController with 'Container View' element (pick one in Interface Builder from the Object library (where all other views are) ).
This way you can use UITableViewController like ordinary view (in terms of positioning) and compose any layout you need without overwritting existing table view code
EDIT:
to further expand my answer, here are the steps to accomplish the described approach:
Open you storyboar in interface builder
Drag'n'drop a new ViewController element to the storyboard from Object Library to add a new controller.
As a child view, drag'n'drop Container View from Object Library and place it anywhere inside the ViewController
Container View creates another view controller and "embedded" segue as a connection. It's save to delete this viewcontroller and to connect the Container View with the required view controller (as per the questions it's UITableViewController)
To connect Container View with UITableViewController just select the container view and control+drag to the UITableViewController - select "Embed" in the popup menu
Now the controller will display inside the Container View with respect to the container's position and boundaries.
It's possible to get a link to the embeeded view controller - the system will fire "prepareForSegue" method of the host viewcontroller (created on the step 1 above) and the controller is in segue.destinationViewController property - one can customize it as required. Just make sure to set an identifier to the "embedded" segue in interface builder - this is the same process just like for any other segues.
Define a custom UIView in storyboard or xib, have a IBOutlet reference for that UIView in View Controller. In -(void)viewWillAppear:(BOOL)animated method write [self.tableView.superview addSubview:filterHeaderView];, here filterHeaderView is the IBOutlet reference for my header view which I want to add as fixed header in my tableview.
Related
I have a UIViewController(in Objective C) which contains various subviews. Now I have added a new UIView(swift file and a xib file) as a subview to this view controller. In this UIView I have dragged and dropped UITableView and set datasource and delegate to file owner. Also, I have created a separate UITableViewCell class(in swift).
I want to add this UITableViewCell in UITableView(created in UIView). And add this entire view as a subview to UIViewController. The frames of the UIView are set in view controller
When I added this UIView as subview tables and cells are not displayed and tried various methods but either the app crashes by tableview as nil or the cell is not loaded at all and rows are not displayed
Please help me out!!!
Thank you
Code Snippet:
UIViewController
- (void)addNewView() {
NewUIView *view = [[NewUIView alloc] initWithFrame:CGRectMake(0, 200, SCREEN_WIDTH, self.view.bounds.size.height)];
[self.contentScrollView addSubview:view];
}
UIView(NewUIView)
Here I have added an outlet of UITableView with delegate methods of UITableView in extension(connected delegate and datasource from xib file to file owner).
And add cell
UITableViewCell
I want want to add this cell to UITableView In UIView.
Hi Is it possible to combine to combine a UIView with a UITableView and reference it as a single object? I currently have a both combined as a Stack, but I feel there is a better way to do it. Thanks
They are two different objects, one property cannot reference two different views. You can however use an IBOutlet for both views in your ViewController, or within any ancestor view.
class MyViewController : UIViewController {
#IBOutlet var tableView : UITableView!
}
Then, you can just drag a reference outlet for that specific view from Storyboard to the view controller. For information on that:
https://developer.apple.com/library/ios/recipes/xcode_help-IB_connections/chapters/CreatingOutlet.html
Note that I did not add a property for your UIView because I assumed it is already the main view of the controller. Similarly, you can subclass your main view, add an outlet for the table view, and use that as the reference outlet.
In my opinion, there are two way to resolve your require:
Option 1. use table view header view. I suppose you want to combine a view named topView with a table view. call tableView.tableHeaderView = topView
Option 2. add topView to table view as a subview, then adjust contentInset of table view. Just like:
UIEdgeInsets tableViewInsets = self.tableView.contentInset;
tableViewInsets.top = topView.frame.size.height;
self.tableView.contentInset = tableViewInsets;
This question should not be mixed up with this here.. These are two different things.
There is a good example how to use a UITableView Header on SO.
This all works fine and the main header is fixed on top as long as the style is set to plain.
But if I use sections, the main header no longer sticks to top and moves away while scrolling to the bottom.
In this method, I am returning the header for each section.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
In this method I am setting the height for the header section above:
- (CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section
In this method, I am setting the real table header.
- (void)viewWillAppear:(BOOL)animated {
...
self.recordTableView.tableHeaderView = headerView;
}
Is it even possible having a fixed table header, while using sections?
What is an alternative solution to this please?
If you want a UITableViewController (static cells/keyboard handling) and have a fixed header then you should use Containment. You can do this from a Storyboard by setting up a UIViewController with your fixed header and then using a Container View to embed the UITableViewController.
Once you have your containing view setup, you right-click drag from the Container View to the View Controller you want to embed - the UITableViewController in this case.
You can access and get a reference to the contained View Controller (the UITableViewController) from the Container View Controller by implementing the prepareForSegue:sender: method.
There’s no way to maintain the header of a tableView fixed, but
an useful approach when you need a unique header, is to use a UIViewController rather than a UITableViewController, and set the header (UIView) out from the tableView.
Something like this:
If you want to keep the class as a UITableViewController you can add your header as a subview to the tableview's superview. You will have to also push the tableview top inset down so your headerview doesnt hide the table.
Here is a sample code to put inside your tableViewController subclass (This example assumes your tableview controller is inside a navigation controller, so it pushes the view to below the navigation bar):
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.automaticallyAdjustsScrollViewInsets = NO;
}
-(void)addHeaderView{
CGFloat yPosition = self.navigationController.navigationBar.frame.origin.y + self.navigationController.navigationBar.frame.size.height;
mainHeaderView = [[UIView alloc] init];
const CGFloat mainHeaderHeight = 44;
[mainHeaderView setFrame:CGRectMake(0, yPosition, self.view.frame.size.width, mainHeaderHeight)];
mainHeaderView.backgroundColor = [UIColor redColor];
[self.tableView.superview addSubview:mainHeaderView];
[self.tableView setContentInset:UIEdgeInsetsMake(yPosition + mainHeaderHeight, self.tableView.contentInset.left, self.tableView.contentInset.bottom, self.tableView.contentInset.right)];
}
I haven't done this, but the first thing I would think to try is to place my tableview in a UIView and make my own header there in that UIView. Seems a trivial matter to make that view appear to be the header of the table and it would certainly stay put.
After many years of avoiding Interface Builder like the plague I decided to give it a chance. It's not easy.
Take UITableViewHeaderFooterView for example. Like UITableViewCell, it has a contentView property. Unlike UITableViewCell, it doesn't have a template in the Interface Builder object library.
How are we supposed to use Interface Builder to create a UITableViewHeaderFooterView with the content inside contentView? The fact that registerNib:forHeaderFooterViewReuseIdentifier: exists makes me think this should be possible somehow.
This is the closest I got to define a UITableViewHeaderFooterView with IB:
a. Create a UITableViewHeaderFooterView subclass (MYTableViewHeaderFooterView).
b. Create a nib file for the contentView only (MYTableViewHeaderFooterContentView).
c. Override initWithReuseIdentifier: in MYTableViewHeaderFooterView to load the view defined in the nib file.
- (instancetype)initWithReuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithReuseIdentifier:reuseIdentifier];
if (self)
{
NSArray* objects = [[NSBundle mainBundle] loadNibNamed:#"MYTableViewHeaderFooterView"
owner:self
options:nil];
UIView *nibView = [objects firstObject];
UIView *contentView = self.contentView;
CGSize contentViewSize = contentView.frame.size;
nibView.frame = CGRectMake(0, 0, contentViewSize.width, contentViewSize.height);
[contentView addSubview:nibView];
}
return self;
}
d. Register the MYTableViewHeaderFooterView class instead of the nib file:
[self.tableView registerClass:[MYTableViewHeaderFooterView class] forHeaderFooterViewReuseIdentifier:#"cell"];
I just did this with a footer and a NIB file:
Create an empty NIB file with name CustomFooterView.xib.
Edit the NIB file in the Interface Builder and change the topmost UIView custom class to UITableViewHeaderFooterView.
Disable Auto Layout in the NIB.
Set the background color of UITableViewHeaderFooterView view to Default.
Make the view freeform and correct size (for example 320 x 44).
In your UITableViewController viewDidLoad register the NIB file to be used with a reuse identifier:
[self.tableView registerNib:[UINib nibWithNibName:#"CustomFooterView" bundle:nil] forHeaderFooterViewReuseIdentifier:#"Footer"];
In your UITableViewController's tableView:viewForFooterInSection: use the Footer identifier to fetch and return the view:
- (UIView *)tableView:(UITableView *)tableView viewForFooterInSection:(NSInteger)section
{
if (section == 2)
return [tableView dequeueReusableHeaderFooterViewWithIdentifier:#"Footer"];
return nil;
}
Just use the UITableViewCell template in IB. Change the class to UITableViewHeaderFooterView. Here you have it... with a contentView.
I found easier way.
1) Create subclass of UITableViewCell and set your xib file
2) In your header file change superclass from UITableViewCell to UITableViewHeaderFooterView
That's it.
This solution works well, especially if you want it to work correctly in relation to Readable Content Guides (introduced in iOS 9). Instead of creating a UITableViewHeaderFooterView, it simply returns a custom UIView (from a XIB) when it is required:
Create a new class that subclasses UIView ("AwesomeHeaderView") and create your outlets:
class AwesomeHeaderView: UIView {
#IBOutlet var myCustomLabel: UILabel!
}
Create a XIB file ("MyNewHeader.xib") with a UIView as
the parent view. Change the parent UIView's class type to your newly created custom class ("AwesomeHeaderView"). As required, add any additional views as it's children and link outlets etc. (NB: To ensure views comply to the new Readable Content Guides I check the boxes "Preserve Superview Margins" and "Follow Readable Width" on all objects).
In your UIViewController (or
UITableViewController) call the following:
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
guard let headerView = NSBundle.mainBundle().loadNibNamed("MyNewHeader", owner: nil, options: nil).first as? AwesomeHeaderView else {
return nil
}
// configure header as normal
headerView.backgroundColor = UIColor.redColor()
headerView.myCustomLabel.textColor = UIColor.whiteColor()
headerView.myCustomLabel.text = "Hello"
return header
}
I also experienced the deprecation warning above and an inability to set background color, etc.. Eventually, I found that according to Apple's documentation,
You can use [the UITableViewHeaderFooter] class as-is without subclassing in most cases. If you have custom content to display, create the subviews for your content and add them to the view in the contentView property.
Following that suggestion, I took these steps:
I created a xib with a view that extends only UIView--not UITableViewHeaderFooter.
In viewDidLoad I registered the built-in UITableViewHeaderFooter class
tableView.registerClass(UITableViewHeaderFooterView.self, forHeaderFooterViewReuseIdentifier: "sectionHeader")
In the viewForHeaderInSection delegate of my UITableViewController, I dequeued the header view by that identifier and checked to see if the header view already contained a subview. If not, I load my xib and add it. Then I set my text as needed.
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = self.tableView.dequeueReusableHeaderFooterViewWithIdentifier("sectionHeader")!
if header.contentView.subviews.count == 0 { header.contentView.addSubview(loadMyNib()) }
let myView = header.contentView.subviews[0] as! MyView
myView.label.text = "..."
This seems to work, leverages reuse, and does not produce any warnings.
https://developer.apple.com/library/prerelease/tvos/documentation/UIKit/Reference/UITableViewHeaderFooterView_class/index.html#//apple_ref/occ/instm/UITableViewHeaderFooterView/prepareForReuse
Following workarounds enable me to drag-assign IB items to code as variables. UITableViewHeaderFooterView doesnt allow that out of the box.
create a (New File/CocoaTouchClass) UITableViewHeaderFooterView
.h.m.xib normally
temporarily rename superclass from UITableViewHeaderFooterView to UIView. Drag-assign your UI items to code as needed, IB will assign key-value
correctly, revert back to UITableViewHeaderFooterView when done.
in your tableview, use registerNib: to register instead of registerClass:. prepare the rest of tableview normally (ie:dequeue).
An awful hack I figured out is to create a IBOutlet contentView ih your headerFooter class and hook it up to the your "content view" in the xib (Have your xib laid out like a tableViewCell, View->contentView->mystuff).
You'll get warnings, ok ready for the hack...
Delete the IBOutlet and it will all work.
The best way I found:
Create your HeaderFooterView(.h/.m), such as header
Create a xib(view), name the same
Make the root view class as your class(header)
[important] Connect all outlets to the root view, not the File's Owner
Last, register your nib to table(All done).
I have put an UITableView inside a UIViewController and have copied code from UITableViewController over to my UIViewController to have it fulfill "Table data protocols" with UITableViewControllers default implementation and am following http://developer.apple.com/library/IOs/#documentation/UserExperience/Conceptual/TableView_iPhone/CreateConfigureTableView/CreateConfigureTableView.html#//apple_ref/doc/uid/TP40007451-CH6-SW10s chapter "Creating a Table View Programmatically" (with using ViewDidLoad instead of LoadView as I use IB and have a nib, I created an outlet for the UITableView named tableView).
Now in above links chapter the UITableView is assigned to self.view which is the UIView property of the UIViewController. Then scrolling the UITableView is working but the UITableView fills the entire screen hiding other view content such as an UIToolbar - probably as it is the sole content after being assigned to the view property.
If I omit the assignment, the other view content is in place an everything is sized properly, however the UITableView doesn't scroll.
How do I achieve scrolling of the UITableView inside an UIViewController with the UIVC having static content (such as a toolbar)?
Here's the relevant code:
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
tableView.delegate = self;
tableView.dataSource = self;
[tableView reloadData];
//self.view = tableView;
}
If you have other content in the view controller that you are adding in interface builder then this will be part of the UIViewController's .view property - when you assign the table view to this, you are removing all that content.
If your table view is appearing normally but is not scrolling (how about selection? Does that work?) you may have disabled user interaction on it.
You have mentioned in comments that there is nothing but empty cells in your table view - I think this is the cause of your problem. I have just created a sample project where I have added in a table view as a subview of the view controller's view, and scrolling is fine - this is with me populating 100 dummy rows in there. If I don't return anything for the datasource and delegate methods, then the table view does not scroll.