UITableViewHeaderFooterView with IB - ios

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).

Related

UIView not filling table views tableHeaderView property swift

I have a very peculiar situation and want to know why its happening. I have an edit box and button in a view that I put into a tableview's tableHeaderView property. I call this view InputToListCell.
Now when the view extends UIView I get the following:
i.e all I change in the code is:
class InputToListCell: UIView{
//Code Here
}
I then change InputToListCell to extend UITableViewCell and get the following:
i.e
class InputToListCell: UITableViewCell{
//Code Here
}
Why is this behaviour occurring? I cant see why because UITableViewCell extends UIView. Any thoughts?
Update:
Based on a comment made here are the constraints for the InputToListCell:
I basically pin both the edit text's and Add buttons constraints to the margin, except for the edit text's trailing value.
In short
Use UITableViewCell for table view elements.
self.tableView.tableHeaderView =
tableView.dequeueReusableCellWithIdentifier("sectionIdentifier")
Discussion
Using UITableViewCell objects for all table view elements not only provide an umbrella behavior when presented inside a UITableView, but will also allow loading from a .storyboard or .xib, an added bonus and cohesion.
Thus, even though tableHeaderView (and viewForHeaderInSection for that matter) is informally just a UIView, you can return a UITableViewCell. According to this post, to take advantage of Autolayout in tableHeaderView, you need to do a bit of gymnastics:
if let tableHeaderView = tableView.dequeueReusableCellWithIdentifier("tableHeaderViewIdentifier") {
tableHeaderView.setNeedsLayout()
tableHeaderView.layoutIfNeeded()
let height = tableHeaderView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
var frame = tableHeaderView.frame
frame.size.height = height
tableHeaderView.frame = frame
self.tableView.tableHeaderView = tableHeaderView
}
From a different post:
For a complete example on how to use .storyboard or .xib for headers, including an Xcode project, see https://stackoverflow.com/a/32261262/218152

How to add an UIView from a XIB file as a subview to another Xib file

I am trying to add a custom UIView that I created in XIB, to my view controller in my main.storyboard as a subview. How can I do this?
matchScrollView (tag 1) is the UIScrollView in view controller in main.storyboard, while matchView (tag 2) is the custom UIView I created in another XIB file.
With the press of a button i want to have the custom UIView added to the UIScrollView as a subview. But how can i actually make it show up on display? I guess i have yet to alloc and init it, along with indicate position and such, but how can i do that? I tried different ways without success. I can create UIViews programmatically, but have yet to find a way to just load the UIView from XIB.
-(IBAction) buttonTapped:(id)sender {
UIScrollView *matchScrollView = (UIScrollView *) [self.view viewWithTag:1];
UIView *matchView = (UIView *) [self.view viewWithTag:2];
[matchScrollView addSubview:matchView];
}
The reason that I am creating my custom UIView in another XIB file instead of directly implementing it on my main.storyboard view controller, is because I want to re-use the same view multiple times. So the UIScrollView has a numerous subviews of UIViews.
I was hoping I could create numerous instances of MatchView and add them all to matchScrollView as subviews.
The issue you are having is completely normal. The way Apple designed it doesn't allow to reuse custom views with their own xib into other xibs.
Lets say you have a custom view named HeaderView with a custom xib named HeaderView.xib. And lets say you want to be able to, in another xib named GlobalView.xib, drag a subview and specify its class to be of type HeaderView expecting it to load that view from HeaderView.xib and insert it inplace. You can do it like this:
A) Make sure File's Owner in HeaderView.xib is set to be HeaderView class.
B) Go to your GlobalView.xib, drag the subview and make it of class HeaderView.
C) In HeaverView.m implement initWithCoder, if after loading the view there aren't subviews means it got loaded from GlobalView, then load it manually from the correct nib, connect the IBOutlets and set the frame and autoresizingmasks if you want to use the GlobalView's frame (this is usually what you want).
- (id)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self.subviews.count == 0) { //means view got loaded from GlobalView.xib or other external nib, cause there aren't any subviews
HeaverView *viewFromNib = [[NSBundle mainBundle] loadNibNamed:#"HeaverView" owner:self options:nil].firstObject;
//Now connect IBOutlets
self.myLabel1 = viewFromNib.myLabel1;
self.myLabel2 = viewFromNib.myLabel2;
self.myLabel3 = viewFromNib.myLabel3;
[viewFromNib setFrame:self.bounds];
[viewFromNib setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight];
[self addSubview:viewFromNib];
}
return self;
}

How to embed a custom view xib in a storyboard scene?

I'm relatively new in the XCode/iOS world; I've done some decent sized storyboard based apps, but I didn't ever cut me teeth on the whole nib/xib thing. I want to use the same tools for scenes to design/layout a reusable view/control. So I created my first ever xib for my view subclass and painted it up:
I have my outlets connected and constraints setup, just like I'm used to doing in the storyboard. I set the class of my File Owner to that of my custom UIView subclass. So I assume I can instantiate this view subclass with some API, and it will configured/connected as shown.
Now back in my storyboard, I want to embed/reuse this. I'm doing so in a table view prototype cell:
I've got a view. I've set the class of it to my subclass. I've created an outlet for it so I can manipulate it.
The $64 question is where/how do I indicate that it's not enough to just put an empty/unconfigured instance of my view subclass there, but to use the .xib I created to configure/instantiate it? It would be really cool, if in XCode6, I could just enter the XIB file to use for a given UIView, but I don't see a field for doing that, so I assume I have to do something in code somewhere.
(I do see other questions like this on SO, but haven't found any asking for just this part of the puzzle, or up to date with XCode6/2015)
Update
I am able to get this to kind of work by implementing my table cell's awakeFromNib as follows:
- (void)awakeFromNib
{
// gather all of the constraints pointing to the uncofigured instance
NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
}]];
// fetch the fleshed out variant
ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:#"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
// ape the current placeholder's frame
fromXIB.frame = self.progressControl.frame;
// now swap them
[UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
// recreate all of the constraints, but for the new guy
for (NSLayoutConstraint *each in progressConstraints) {
id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
[self.contentView addConstraint: constraint];
}
// update our outlet
self.progressControl = fromXIB;
}
Is this as easy as it gets then? Or am I working too hard for this?
You're almost there. You need to override initWithCoder in your custom class you assigned the view to.
- (id)initWithCoder:(NSCoder *)aDecoder {
if ((self = [super initWithCoder:aDecoder])) {
[self addSubview:[[[NSBundle mainBundle] loadNibNamed:#"ViewYouCreated" owner:self options:nil] objectAtIndex:0]];
}
return self; }
Once that's done the StoryBoard will know to load the xib inside that UIView.
Here's a more detailed explanation:
This is how your UIViewController looks like on your story board:
The blue space is basically a UIView that will "hold" your xib.
This is your xib:
There's an Action connected to a button on it that will print some text.
and this is the final result:
The difference between the first clickMe and the second is that the first was added to the UIViewController using the StoryBoard. The second was added using code.
You need to implement awakeAfterUsingCoder: in your custom UIView subclass. This method allows you to exchange the decoded object (from the storyboard) with a different object (from your reusable xib), like so:
- (id) awakeAfterUsingCoder: (NSCoder *) aDecoder
{
// without this check you'll end up with a recursive loop - we need to know that we were loaded from our view xib vs the storyboard.
// set the view tag in the MyView xib to be -999 and anything else in the storyboard.
if ( self.tag == -999 )
{
return self;
}
// make sure your custom view is the first object in the nib
MyView* v = [[[UINib nibWithNibName: #"MyView" bundle: nil] instantiateWithOwner: nil options: nil] firstObject];
// copy properties forward from the storyboard-decoded object (self)
v.frame = self.frame;
v.autoresizingMask = self.autoresizingMask;
v.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
v.tag = self.tag;
// copy any other attribtues you want to set in the storyboard
// possibly copy any child constraints for width/height
return v;
}
There's a pretty good writeup here discussing this technique and a few alternatives.
Furthermore, if you add IB_DESIGNABLE to your #interface declaration, and provide an initWithFrame: method you can get design-time preview to work in IB (Xcode 6 required!):
IB_DESIGNABLE #interface MyView : UIView
#end
#implementation MyView
- (id) initWithFrame: (CGRect) frame
{
self = [[[UINib nibWithNibName: #"MyView"
bundle: [NSBundle bundleForClass: [MyView class]]]
instantiateWithOwner: nil
options: nil] firstObject];
self.frame = frame;
return self;
}
A pretty cool and reusable way of doing this Interface Builder and Swift 4:
Create a new class like so:
import Foundation
import UIKit
#IBDesignable class XibView: UIView {
#IBInspectable var xibName: String?
override func awakeFromNib() {
guard let name = self.xibName,
let xib = Bundle.main.loadNibNamed(name, owner: self),
let view = xib.first as? UIView else { return }
self.addSubview(view)
}
}
In your storyboard, add a UIView that will act as the container for the Xib. Give it a class name of XibView:
In the property inspector of this new XibView, set the name of your .xib (without the file extension) in the IBInspectable field:
Add a new Xib view to your project, and in the property inspector, set the Xib's "File's Owner" to XibView (ensure you've only set the "File's Owner" to your custom class, DO NOT subclass the content view, or it will crash), and again, set the IBInspectable field:
One thing to note: This assumes that you're matching the .xib frame to its container. If you do not, or need it to be resizable, you'll need to add in some programmatic constraints or modify the subview's frame to fit. I use snapkit to make things easy:
xibView.snp_makeConstraints(closure: { (make) -> Void in
make.edges.equalTo(self)
})
Bonus points
Allegedly you can use prepareForInterfaceBuilder() to make these reusable views visible in Interface Builder, but I haven't had much luck. This blog suggests adding a contentView property, and calling the following:
override func prepareForInterfaceBuilder() {
super.prepareForInterfaceBuilder()
xibSetup()
contentView?.prepareForInterfaceBuilder()
}
You just have to drag and drop UIView in your IB and outlet it and set
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:#"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]
Step
Add New File => User Interface => UIView
Set Custom Class - yourUIViewClass
Set Restoration ID - yourUIViewClass
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:#"yourUIViewClass" owner:self options:nil] firstObject];
[self.view addSubview:yourView]
Now you can customize view as you want.
I've been using this code snippet for years. If you plan on having custom class views in your XIB just drop this in the .m file of your custom class.
As a side effect it results in awakeFromNib being called so you can leave all your init/setup code in there.
- (id)awakeAfterUsingCoder:(NSCoder*)aDecoder {
if ([[self subviews] count] == 0) {
UIView *view = [[NSBundle mainBundle] loadNibNamed:NSStringFromClass([self class]) owner:nil options:nil][0];
view.frame = self.frame;
view.autoresizingMask = self.autoresizingMask;
view.alpha = self.alpha;
view.translatesAutoresizingMaskIntoConstraints = self.translatesAutoresizingMaskIntoConstraints;
return view;
}
return self;
}
Create xib file
File > New > New File > iOS > User Interface > View
Create custom UIView class
File > New > New File > iOS > Source > CocoaTouch
Assign the xib file's identity to the custom view class
In viewDidLoad of the view controller initialize the xib and its associated file using loadNibNamed: on NSBundle.mainBundle and the first view returned can be added as a subview of self.view.
The custom view loaded from the nib can be saved to a property for setting the frame in viewDidLayoutSubviews. Just set the frame to self.view's frame unless you need to make it smaller than self.view.
class ViewController: UIViewController {
weak var customView: MyView!
override func viewDidLoad() {
super.viewDidLoad()
self.customView = NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)[0] as! MyView
self.view.addSubview(customView)
addButtonHandlerForCustomView()
}
private func addButtonHandlerForCustomView() {
customView.buttonHandler = {
[weak self] (sender:UIButton) in
guard let welf = self else {
return
}
welf.buttonTapped(sender)
}
}
override func viewDidLayoutSubviews() {
self.customView.frame = self.view.frame
}
private func buttonTapped(button:UIButton) {
}
}
Also, if you want to talk back from the xib to your UIViewController instance then create a weak property on the custom view's class.
class MyView: UIView {
var buttonHandler:((sender:UIButton)->())!
#IBAction func buttonTapped(sender: UIButton) {
buttonHandler(sender:sender)
}
}
Here's the project on GitHub
A little bit more swifty version of #brandonscript 's idea with early return:
override func awakeFromNib() {
guard let xibName = xibName,
let xib = Bundle.main.loadNibNamed(xibName, owner: self, options: nil),
let views = xib as? [UIView] else {
return
}
if views.count > 0 {
self.addSubview(views[0])
}
}
While I don't recommend the path you're going down it can be done by placing an "embedded view controller view" where you want the view to appear.
Embed a view controller that contains a single view -- the view you want to be reused.
It's been a while on this one, and I've seen a number of answers go by. I recently revisited it because I had just been using UIViewController embedding. Which works, until you want to put something in "an element repeated at runtime" (e.g. a UICollectionViewCell or a UITableViewCell). The link provided by #TomSwift led me to follow the pattern of
A) Rather than make the parent view class be the custom class type, make the FileOwner be the target class (in my example, CycleControlsBar)
B) Any outlet/action linking of the nested widgets goes to that
C) Implement this simple method on CycleControlsBar:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
if let container = (Bundle.main.loadNibNamed("CycleControlsBar", owner: self, options: nil) as? [UIView])?.first {
self.addSubview(container)
container.constrainToSuperview() // left as an excise for the student
}
}
Works like a charm and so much simpler than the other approaches.
Here's the answer you've wanted all along. You can just create your CustomView class, have the master instance of it in a xib with all the subviews and outlets. Then you can apply that class to any instances in your storyboards or other xibs.
No need to fiddle with File's Owner, or connect outlets to a proxy or modify the xib in a peculiar way, or add an instance of your custom view as a subview of itself.
Just do this:
Import BFWControls framework
Change your superclass from UIView to NibView (or from UITableViewCell to NibTableViewCell)
That's it!
It even works with IBDesignable to render your custom view (including the subviews from the xib) at design time in the storyboard.
You can read more about it here:
https://medium.com/build-an-app-like-lego/embed-a-xib-in-a-storyboard-953edf274155
And you can get the open source BFWControls framework here:
https://github.com/BareFeetWare/BFWControls
And here's a simple extract of the code that drives it, in case you're curious:
https://gist.github.com/barefeettom/f48f6569100415e0ef1fd530ca39f5b4
Tom 👣
The "correct" answer is that you are not meant to make re-usable views with corresponding nibs. If a view subclass is valuable as a reusable object it rarely will need a nib to go with it. Take for example every view subclass provided by UIKit. Part of this thinking is a view subclass that is actually valuable wont be implemented using a nib, which is the general view at Apple.
Usually when you use a view in nib or storyboard you will want to tweak it graphically for the given use case anyway.
You might consider using "copy paste" for recreating same or similar views instead of making separate nibs. I this believe accomplishes the same requirements and it will keep you more or less in line with what Apple is doing.

Using Custom Static Table View Cell XIB in Storyboard

I have a custom static UITableViewCell that I want to look exactly the same as a right detail cell (UILabel on the left, UILabel on the right), except I want the right UILabel to be an editable UITextField. Since I want to use this cell in multiple view controllers in my storyboard, I decided the best option would be to create a MyEditableTableViewCell.xib file, along with corresponding *.h,m files. I have two public properties in MyEditableTableViewCell.h:
#property (weak, nonatomic) IBOutlet UILabel *textLabel;
#property (weak, nonatomic) IBOutlet UITextField *detailTextField;
These IBOutlets are connected to the UILabel and UITextField in the .xib file. In my storyboard, where I have a custom UITableViewController subclass, I change the class of the necessary UITableViewCell to MyEditableTableViewCell. I have a property in my view controller that is:
#property (weak, nonatomic) IBOutlet MyEditableTableViewCell *myCell;
However, when I run the app, the cell appears as simply a blank cell. Running some checks on it, I see that [myCell isKindOfClass:[MyEditableTableViewCell class]] returns true. However, the UITextField never seems to get instantiated. When I try to alter myCell.detailTextField.text, nothing happens, and myCell.detailTextField appears to be nil.
Any help would be appreciated!
How are you creating instances of MyEditableTableViewCell? My guess is you're doing [[MyEditableTableViewCell alloc] init] instead of loading them from the nib.
You don't need that property in your view controller. You need to register your cell nib with the table view using -[UITableView registerNib:forCellReuseIdentifier:]. Do that in your viewDidLoad. Then, when you send dequeueReusableCellWithIdentifier: to the table view, it will instantiate the nib, creating a MyEditableTableViewCell, and return it to you.
UPDATE
If you're laying out static cells in a storyboard, using a xib to lay out a cell is somewhat difficult. The simplest thing to do is simply lay out the cell's subviews in the storyboard. You can copy and paste the subviews to each cell if you have a few of the same type.
If you really want to use a xib, the easiest way is to structure your cell's view hierarchy like this:
MyEditableTableViewCell (in storyboard)
|
+- cell's content view (created automatically by UIKit)
|
+- UIView (top-level view of the xib)
|
+- UILabel (textLabel)
|
+- UITextField (detailTextField)
So you set the cell class in the storyboard to MyEditableTableViewCell. Then you create your xib. You set your xib File's Owner class to MyEditableTableViewCell. The xib does not contain a MyEditableTableViewCell. The top-level view of the xib is just a plain UIView, containing the subviews (the label and the text field).
In -[MyEditableTableViewCell initWithCoder:], after doing self = [super initWithCoder:aDecoder], instantiate the xib, passing self (the cell) as the xib file's owner. The cell can have outlets connected to the label and the text field in the xib.
After instantiating the xib, add the top-level view from the xib as a subview of self.contentView. If you're using auto layout, create constraints betwixt the top-level view and the content view. Otherwise, set the top-level view's frame to the content view's bounds and set the autoresizing mask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight.
Although a static table view will not fetch your cell from the nib, it will still call cellForRowAtIndexPath. There you can dequeue and return your fully unarchived custom cell. Properties on the custom cell that were set in IB, can be fetched by calling super on cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// get an empty cell with properties retained from IB
UITableViewCell *sup = [super tableView:tableView cellForRowAtIndexPath:indexPath];
if ([sup isKindOfClass:[MyCustomCell class]]) {
// fetch a fully unarchived cell
MyCustomCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"foo" forIndexPath:indexPath];
// copy properties over...
[cell copyPropertiesFromCell:(MyCustomCell *)sup];
return cell;
}
return sup;
}
Depending on the situation, this might actually be easier than to mess with IB objects.

Fixed header to UITableview?

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.

Resources