I want to add a tableview-look-a-like-login to my app, but it seems to be not that easy to implement. I tried to accomplish my goal using more then one approach, but i am not sure about which solution is the best.
For example, Dropbox and Facebook have a login page like this.
Here are my 3 approaches :
I added 2 UITextfields to my View (no border) and placed a . png behind, which looks like a tableviewcell. ( Not the best approach cause i want to use real tableviews )
I added a Container View to my ViewController placed a tableview with static Table Views inside. The Problem here is, that i dont know how to access the information inside my viewcontroller?
I added a tableview to my ViewController and used dynamic cells with it. Connected the outlets for delegate and datasource to my viewcontroller and initialized them with the delegate and datasource methods. The Problem here is, that i can not use static table views inside a uiviewcontroller.
Is there any better way of solving this problem ?
I would really like to know how to do this in a more elegant way.
EDIT:
A ContainerViewController basically solved this issue for me some month ago.
After embedding one into the main controller you can access it through the prepareForSegue function and define a protocol-based interface for that specific controller to interact with the embedded controller.
If you want to use static cells inside a regular UIViewController, just add the static cells and design them the way you like in interface builder, then connect the table cells as strong IB outlets (weak won't work, make sure they are strongly referenced). This will work flawlessly if you have a few table cells. Then set the view controller as the data source of the tablet view, implement -(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section to return the number of cells and implement -(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath to return your strongly referenced cell instance for the specified index path. I've used this method for a simple table view in my view controller that had four cells and it is working perfectly. For a large-dynamic data set, I definitely do not recommend this approach but for small, static tables, this does the job right.
I have an idea how to solve this. I think it's a clean way to do so. You do not need storyboard for this controller.
Make your controller subclass UITableViewController like so:
#interface YourViewController : UITableViewController
Then in your viewDidLoad you create the instances of the cells:
- (void) viewDidLoad {
usernameCell = [YourTextFieldCell new];
passwordCell = [YourTextFieldCell new];
}
The YourTextFieldCell is of course your own subclass of a UITableViewCell, which could be something like this:
#implementation YourTextFieldCell {
UITextField textField;
}
- (id) init {
self = [super init];
if (self) {
// Adjust the text's frame field to your liking
textField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, 200, 20)];
[self addSubview:textField];
}
}
// A getter method to access the textfield from the outside
- (UITextField *) textField {
return textField;
}
#end
Back in YourViewController:
- (NSInteger) tableView:(UITableView *) tv numberOfRowsInSection:(NSInteger) section {
return 2;
}
- (UITableViewCell *) tableView:(UITableView *) tv cellForRowAtIndexPath:(NSIndexPath *) indexPath {
if (indexPath.row == 0) {
return usernameCell;
} else if (indexPath.row == 1) {
return passwordCell;
}
return nil;
}
Do you get where I am going with this? This is how I think you should do it! Good luck!
I think your approach 2 is the best. If you need to access information in the table view controller, from your UIViewController (which will be the parent view controller), you can get a reference to that table view controller with self.childViewControllers.lastObject. In the viewDidLoad method of the UIViewController subclass, you could set yourself as the delegate of the table view with this line if you want:
[[(UITableViewController *)self.childViewControllers.lastObject tableView] setDelegate:self];
That way, you could implement the tableView:didSelectRowAtIndexPath: method in the view controller, which will get the information I'm guessing you need.
If you go with your option 2) using a storyboard and have a ContainerView containing your own subclass of UITableViewController with static cells then you can implement the prepareForSegue: method in your parent ViewController to take a reference to the UITableViewController (it'll be the destinationController of the segue) and also to pass itself down to the UITableViewController subclass if necessary (which should hold onto it with a weak reference).
Disclaimer - This answer will work for any size of UITableView, but if you're just making a login view, Tom's answer will work quite well.
I'm not sure if this will help, but what I did for this was create my own UITableView-esque subclass with a UITableViewCell-esque subclass as well.
This may not be what you want to hear, but I find what I made to be really helpful, since I've used it a number of times now. Basically, you have a UIView with the stylistic approach for the different types (10.0f - 20.0f cornerRadius and a 1px border (divide by UIScreen's scale property for retina). As for the cell, you'll want to have a full sized UIButton on it that responds to your table view for the touch events either with a delegate or by setting the target and tag inside your table view's class.
Last, you'll have a delegate system just like the UITableView for your information for building the specific tables.
In short, you'll need:
2 UIView subclasses (TableView and TableViewCell)
2 Delegates/Protocols (TableViewDataSource and TableViewDelegate)
Optionally
1 Delegate (TableViewCellResponseDelegate)
1 NSObject Subclass (Contains all of the information needed in each cell - Ease of use)
I found Can's solution to be the best / easiest, but unfortunately it breaks in XCode 5.1 --
I found a workaround which builds off the same basic idea, but unfortunately requires a little more involvement: http://www.codebestowed.com/ios-static-tableview-in-uiviewcontroller/
To summarize, you can add TableViewCells directly to views (and create IBOutlets from them, etc), but in order for them to get "moved" to the TableView properly, you need to remove them from the view in code, and you also need to set Auto-Layout constraints in IB.
Related
I'm using Xcode Version 5.0 (5A1413) and targeting iOS7 for iPhone. I have nothing but a UIViewController with a Table View on it. Everything I've found says to just set the table's dataSource and delegate to the ViewController. No matter what I do the app just crashes immediately. The view never loads even though there's no code written manually at all. Is it no longer possible to put a table onto a non TableViewController?
You should implement all required methods from UITableViewDataSource for preventing crash:
#required
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
I think you did a common mistake from beginners. You deleted the main view in Interface Builder and then added a Table View.
You have to know that the UIViewController class has a UIView property called view. By default this property is set to the view created with the nib (or Storyboard) file.
To say that the Table View is the main viewcontroller's view you have to right click from the file owner to the tableview and select "view".
Another method if your not comfortable yet with this method is to set the tableview in the first view.
Here is the answer - How to add static TableView to ViewController
Really you need to implement required methods.
There is no such restrictions with iOS 7.0. You can still have a UIViewController in-act as a data source and delegate for UITableView. You just need to implement all the mandatory methods set by these protocols. Could you please update your question with your code and the exception causing the crash. Without this information it would be hard to provide a solution.
I am especially interested in seeing your cellForRowAtIndexPath the way you are constructing cells and reusing them.
Add the Delegate and Datasource to the interface:
#interface MyViewController : UIViewController
<UITableViewDataSource, UITableViewDelegate>
I want to be able to share data between table views for an app that I'm making. The reason for this is that I want to be able to tell a subview which table row was selected so that I don't have to make a bunch of views and I can just test to see what the integer variable was. I watched a video tutorial on how to do this but they did not use tableviews. So when I tried this it did not seem to work. I used the app delegate as a "data center" that held that variables and then I tried to assign values to the variables in didSelectRowAtIndexPath method. (Pushing the new view works fine by the way it's just the shared application)
Here's the code for the first tableview where I assign the variable to a number.
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
NSUInteger row = [indexPath row];
ApplicationAppDelegate *appDelegate = (ApplicationAppDelegate *)[[UIApplication sharedApplication] delegate];
appDelegate.rowPicked = row;
SecondLevelViewController *nextController = [self.controllers objectAtIndex:row];
[self.navigationController pushViewController:nextController animated:YES];
}
In the app delegate I did this and I synthesized it in the .m file:
#property (nonatomic) NSInteger rowPicked;
As well as other NSIntegers that I needed.
Any suggestions? If you think I'm doing this totally wrong could you please enlighten me with specific instructions or a link to a website or video tutorial?
Thank you all!
Here's how I usually accomplish this:
I don't use the AppDelegate for this. The logic and model data for both list and detail should reside in classes that make sense. In this case, we'll use the list and detail view controller classes themselves.
I'll begin by creating a UITableViewCell sublcass for the list view's row. In that sublcass, I'll create an ivar that houses the "entity" or whatever data the cell will need to display it's information. This can be an NSManagedObject or even an NSDictionary.
I'll override the setter of that ivar so that when data is set on the UITableViewCell, it updates the cell outlets to display it correctly. Notice how I keep the logic of how the cell is displayed contained completely within the subclass. It's important that you do things like this throughout your application to promote code cleanliness and organization.
In your tableView:didSelectRowAtIndexPath method, you'd then call the UITableView class' cellForRowAtIndexPath: method to return the cell that was selected. You can then cast it to your UITableViewCell subclass and get the entity information you set earlier.
Next, you'll need to create an ivar in your detail view controller. You'll want to set this variable from the tableView:didSelectRowAtIndexPath: method---right before you push the detail view onto the stack.
You should now have the necessary data in your detail view controller sublcass for processing, querying, or whatever.
Hope this helped!
It's really looks bad. Much better add id<$YOUR_PROTOCOL> delegate to SecondLevelViewcontroller and set nextController.delegate = self.
Protocol can looks like
#protocol RowAccessProtocol
#optional
-(NSUInteger)selectedRow;
#end
Your current tableViewController must be created:
#protocol RowAccessProtocol;
#class FirstLevelTableViewController:UITableViewController<RowAccessProtocol>
…
#end
And implementation:
…
-(NSUInteger)selectedRow{
return [self.tableView indexPathForSelectedRow].row;
}
In SecondLevelViewController you can call [self.delegate selectedRow] to get selected row.
I have an app where I need to have two tableviews swapped in and out by once view controller. I currently have it set so the viewcontroller is the delegate and datasource to both, so I use if / else statements in the delegate / datasource methods to determine which tableview to perform the action on like below:
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if (tableView == [self selectAnAlbumTableView])
{
return [[self albums] count];
}
else
{
return ceil([[self album] numberOfAssets] / 4.0);
}
}
However, I saw this in another thread: "One method which I have often used is to actually have the delegates and data source for the two UITableViews be different objects. This way, your view controller doesn't have to switch back and forth, and your code is overall cleaner and simpler." My question is, how would you go about implementing something like this, ie separate objects for the delegate / datasource, and is it better than what I'm currently doing?
See at most elementary level: I would recommend you not to play with two table view controllers .Just use a navigatorcontroller and push a view based on your requirement. Its much easier to call you different view controller classes than play with two table views in same controller.
It depends. Both has its own advantages and disadvantages. I believe both of your tableview are in a viewcontroller. If both delegate and data source are in different class (Mostly inherited from NSObject)then, in delegate method like tablewviewDidSelectRowAtIndexPath you wont be able to push a view controller or access /set label of viewcontroller directly. You have to use your own delegate method from data/delegate source of tableView back to viewcontroller.
I'm using custom headers for my tableview...
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
CustomHeaderController *header = [[CustomHeaderController alloc]
initWithNibName:#"TableHeader" bundle:nil];
header.title.text = #"Test";
return header.view;
}
The title label is never set though. I even tried creating a viewWillAppear method and setting it there, but that didn't work. My outlets are set up too!
Thanks!
SOLUTION: View wasn't load on the return header.view call. Call header.view or add a viewDidLoad method to the class of header to get it to work! Thanks all!
So I'm not sure "title" here means what you think it means. Setting the title on a view controller isn't going to affect what's in the view. If you're trying to set a title on a section, you may just want to use tableView:titleForHeaderInSection. tableView:viewForHeaderInSection is for more complex section headers (like if you wanted to put buttons, or multiple rows of text or something like that). If it matters, you can use both of these methods. I'm not 100% certain on the order they're called, but I'm pretty sure it looks for a viewForHeaderInSection and then if that's nil it goes to titleForHeaderInSection.
The function expects a UIView to be returned, but you appear to be returning a UIViewController. Did you mean to do return header.view?
General Description:
To start with what works, I have a UITableView which has been placed onto an Xcode-generated view using Interface Builder. The view's File Owner is set to an Xcode-generated subclass of UIViewController. To this subclass I have added working implementations of numberOfSectionsInTableView: tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: and the Table View's dataSource and delegate are connected to this class via the File Owner in Interface Builder.
The above configuration works with no problems. The issue occurs when I want to move this Table View's dataSource and delegate-implementations out to a separate class, most likely because there are other controls on the View besides the Table View and I'd like to move the Table View-related code out to its own class. To accomplish this, I try the following:
Create a new subclass of UITableViewController in Xcode
Move the known-good implementations of numberOfSectionsInTableView:, tableView:numberOfRowsInSection: and tableView:cellForRowAtIndexPath: to the new subclass
Drag a UITableViewController to the top level of the existing XIB in InterfaceBuilder, delete the UIView/UITableView that are automatically created for this UITableViewController, then set the UITableViewController's class to match the new subclass
Remove the previously-working UITableView's existing dataSource and delegate connections and connect them to the new UITableViewController
When complete, I do not have a working UITableView. I end up with one of three outcomes which can seemingly happen at random:
When the UITableView loads, I get a runtime error indicating I am sending tableView:cellForRowAtIndexPath: to an object which does not recognize it
When the UITableView loads, the project breaks into the debugger without error
There is no error, but the UITableView does not appear
With some debugging and having created a basic project just to reproduce this issue, I am usually seeing the 3rd option above (no error but no visible table view). I added some NSLog calls and found that although numberOfSectionsInTableView: and numberOfRowsInSection: are both getting called, cellForRowAtIndexPath: is not. I am convinced I'm missing something really simple and was hoping the answer may be obvious to someone with more experience than I have. If this doesn't turn out to be an easy answer I would be happy to update with some code or a sample project. Thanks for your time!
Complete steps to reproduce:
Create a new iPhone OS, View-Based Application in Xcode and call it TableTest
Open TableTestViewController.xib in Interface Builder and drag a UITableView onto the provided view surface.
Connect the UITableView's dataSource and delegate-outlets to File's Owner, which should already represent the TableTestViewController-class. Save your changes
Back in Xcode, add the following code to TableTestViewController.m:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"Returning num sections");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Returning num rows");
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"Trying to return cell");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
}
cell.text = #"Hello";
NSLog(#"Returning cell");
return cell;
}
Build and Go, and you should see the word Hello appear in the UITableView
Now to attempt to move this UITableView's logic out to a separate class, first create a new file in Xcode, choosing UITableViewController subclass and calling the class TableTestTableViewController
Remove the above code snippet from TableTestViewController.m and place it into TableTestTableViewController.m, replacing the default implementation of these three methods with ours.
Back in Interface Builder within the same TableTestViewController.xib-file, drag a UITableViewController into the main IB window and delete the new UITableView object that automatically came with it
Set the class for this new UITableViewController to TableTestTableViewController
Remove the dataSource and delegate bindings from the existing, previously-working UITableView and reconnect the same two bindings to the new TableTestTableViewController we created.
Save changes, Build and Go, and if you're getting the results I'm getting, note the UITableView no longer functions properly
Solution:
With some more troubleshooting and some assistance from the iPhone Developer Forums, I've documented a solution! The main UIViewController subclass of the project needs an outlet pointing to the UITableViewController instance. To accomplish this, simply add the following to the primary view's header (TableTestViewController.h):
#import "TableTestTableViewController.h"
and
IBOutlet TableTestTableViewController *myTableViewController;
Then, in Interface Builder, connect the new outlet from File's Owner to TableTestTableViewController in the main IB window. No changes are necessary in the UI part of the XIB. Simply having this outlet in place, even though no user code directly uses it, resolves the problem completely. Thanks to those who've helped and credit goes to BaldEagle on the iPhone Developer Forums for finding the solution.
I followed your steps, recreated the project and ran into the same problem. Basically you are almost there. There are 2 things missing (once fixed it works):
You need to connect the tableView of the TableTestTableViewController to the UITableView you have on the screen. As I said before because it is not IBOutlet you can override the tableView property and make it and IBOutlet:
#interface TableTestTableViewController : UITableViewController {
UITableView *tableView;
}
#property (nonatomic, retain) IBOutlet UITableView *tableView;
Next thing is to add a reference to the TableTestTableViewController and retain it in the TableTestViewController. Otherwise your TableTestTableViewController may be released (after loading the nib with nothing hanging on to it.) and that is why you are seeing the erratic results, crashes or nothing showing. To do that add:
#interface TableTestViewController : UIViewController {
TableTestTableViewController *tableViewController;
}
#property (nonatomic, retain) IBOutlet TableTestTableViewController *tableViewController;
and connect that in the Interface Builder to the TableTestTableViewController instance.
With the above this worked fine on my machine.
Also I think it would be good to state the motivation behind all this (instead of just using the UITableViewController with its own UITableView). In my case it was to use other views that just the UITableView on the same screenful of content. So I can add other UILabels or UIImages under UIView and show the UITableView under them or above them.
I just spent many hours pulling my hair out trying to figure out why a UITableView wouldn't show up when when I had it embedded in a separate nib instead of in the main nib. I finally found your discussion above and realized that it was because my UITableViewController wasn't being retained! Apparently the delegate and datasource properties of UITableView are not marked "retain" and so my nib was loading but the controller was getting tossed... And due to the wonders of objective-c I got no error messages at all from this... I still don't understand why it didn't crash. I know that I've seen "message sent to released xxx" before... why wasn't it giving me one of those?!?
I think most developers would assume that structure that they build in an interface builder would be held in some larger context (the Nib) and not subject to release. I guess I know why they do this.. so that the iPhone can drop and reload parts of the nib on low memory. But man, that was hard to figure out.
Can someone tell me where I should have read about that behavior in the docs?
Also - about hooking up the view. First, if you drag one in from the UI builder you'll see that they hook up the view property (which is an IBOutlet) to the table view. It's not necessary to expose the tableView, that seems to get set internally. In fact it doesn't even seem to be necessary to set the view unless you want viewDidLoad notification. I've just broken the view connection between my uitableview and uitableviewcontroller (only delegate and datasource set) and it's apparently working fine.
Yes for some reason (please chime in if anybody knows why...) tableView property of the UITableViewController is not exposed as an IBOutlet even though it is a public property. So when you use Interface Builder, you can't see that property to connect to your other UITableView. So in your subclass, you can create a tableView property marked as an IBOutlet and connect that.
This all seems hacky and a workaround to me, but it seems to be the only way to separate a UITableViewController's UITableView and put it somewhere else in UI hierarchy. I ran into the same issue when I tried to design view where there are things other than the UITableView and that was the way I solved it... Is this the right approach???
I was able to make this work. I built a really nice dashboard with 4 TableViews and a webview with video. The key is having the separate tableView controllers and the IBOutlets to the other tableview controllers defined in the view controller. In UIB you just need to connect the other tableview controllers to the file owner of the view controller. Then connect the tables to the corresponding view controllers for the datasource and delegate.