Here is the story. I have a simple app in which I use 2 tabs, both of them with UITableView. The first tab/view is called "Favorites" and the second one is called "My Profile." In addition I have a custom UITable cell named "CustomViewCell.xib" with an identifier of the same name. FavoritesViewController is a subclass of UITableViewController and that one is running perfectly. But for the ProfileViewController I am using normal ViewController because I don't want the whole view to be UitableView. To make that possible, I the following to ProfileViewController.h:
#interface ProfileViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
#property (nonatomic, strong) IBOutlet UITableView *tableView;
then in the viewDidLoad of ProfleViewController.m file I have:
[self.tableView registerNib:[UINib nibWithNibName:#"CustomViewCell" bundle:nil]
forCellReuseIdentifier:#"CustomViewCell"];
The following methods are implemented just as in the other tab that's working:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.myArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Here I am customizing the cell and returning it.
}
When i run the app, the cells are the right height, but are empty.
To debug I put a break point right before cellForRowAtIndexPath, and the app runs without an error which it shouldn't. So, the program is not even getting to this method. So, I think that's the reason the cells are empty. Do you guys have any idea what might be causing it to skip this particular method? can you also explain it in simpler terms because I'm newbie, you know?
In you ViewDidLoad method, add this code:
[self.tableView setDelegate:self];
[self.tableView setDataSource:self];
Then check again if the breakpoint works
Have you implemented a UITableViewDataSource? A UITableViewController is the delegate and datasource for the Table View it manages so you can define all those methods in the View Controller itself.
For a UITableView that is a subview of a UIViewController, you need to define the Table View's delegate and datasource.
See here: http://www.aboveground.com/tutorials/adding-a-uitableview-to-a-custom-uiview
Related
I'm new to iOS programming so I want to make this thing from scratch to be able understand how the whole thing works. So instead of using the master detail template I did this from the ground up.
I'm having a huge roadblock in terms of passing data between the master view and the detail view. At the moment, whenever I tap on the item of the master view it would look for the first view on the detail view controller, let's call it mainswitchviewcontroller. It's the first ViewController connected via segue to the UINavigationController for the detail controller part (Please refer to image below).
This happens after login where it shows that viewcontroller, I want the user to pick an option before accessing the whole app. which will lead them to the last ViewController of the flow (refer to the image below - red circle).
Now what I'm trying to do is this:
#interface MasterListViewController : UITableViewController{
}
#property (nonatomic) int matNum;
#property (strong, nonatomic) DetailStaffMainContentViewController *detailViewController;
#end
:
:
#implementation MasterListViewController
#synthesize matNum;
:
:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
for(UIViewController *vc in self.splitViewController.viewControllers){
NSLog(#"%#)", vc.title);
}
self.detailViewController.teststring = #"gronk";
}
This is my detailViewController (the one I want to access)
#implementation DetailStaffMainContentViewController
#synthesize tableView;
#synthesize teststring;
#synthesize lblOutput;
Now on select of the row, I assign the teststring which I want to use to populate that label on the controller. That's what I did.
My questions are:
I'm getting an error. Wondering what I was doing wrong?
[MainSwitchBoardViewController setTeststring:]: unrecognized selector
sent to instance 0x7572190
Is this a recommended way of passing data? Or should I just stick with notificationcenters?
Thoughts?
EDIT
Just to give you on the flow of the app:
User logs in (if username does not exist, the floating viewcontroller will pop up and force the user to log in.
Once the user logs in, the pop up goes away and then asks the user what type of role is to be used in the current session (eg. staff or fighter).
If the user picks fighter, it pushes the user to the top VC If the user picks staff, it pushes the user to the bottom VC (collections controller)
FOR Staff: At the moment I'm pushing to the last VC with this [self perfermSegueWithIdentifier:sender:self]
Once the user gets shown the last VC (DetailsStaffView), I want this to be the main Details View where the user does it's transaction. A few more pop ups? (Or maybe another push to another VC for specific transaction needs to be added). But ultimately I don't want the user to access the first Viewcontroller (MasterSwitchBoard)
EDIT 2
This is the main switch board class that controls the first VC after login.
#interface MainSwitchBoardViewController : UIViewController{
}
#property (strong, nonatomic) IBOutlet UILabel *txtLabel;
- (IBAction)btnStaff:(id)sender;
- (IBAction)btnAdmin:(id)sender;
#end
This is the collections VC which atm automatcially pushes to the last VC. In the beginning I was using a notification on this but it seems like it's not necessary so I took it out.
#interface MatListCollectionViewController ()
#end
#implementation MatListCollectionViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
[self performSegueWithIdentifier:#"SeguePushToStaffDetailFromMats" sender:self];
}
And then the details staff
#interface DetailStaffMainContentViewController : UIViewController{
}
#property (strong, nonatomic) IBOutlet UITableView *tableView;
#property (nonatomic, retain) IBOutlet UILabel *lblOutput;
#property (nonatomic, retain) NSString *teststring;
#end
#implementation DetailStaffMainContentViewController
#synthesize tableView;
#synthesize teststring;
#synthesize lblOutput;
I'll tackle those in reverse order:
Is this a recommended way of passing data? Or should I just stick with notificationcenters?
You theoretically could use notification center, but I wouldn't in this case. Personally, I only use NSNotificationCenter if:
I'm doing asynchronous process which has no reasonable way of knowing the state of the view controllers (e.g. I have some background task that's doing some time consuming update of data from a server, and the user could have navigated to just about anywhere in the app by the time I'm ready to notify the view controllers that the update is done); and/or
I potentially have multiple objects that I want to notify of some event.
Neither of those conditions apply here. Certainly you could use NSNotificationCenter, but that should be unnecessary. Explicitly setting properties is generally preferable.
I'm getting an error. Wondering what I was doing wrong?
Unfortunately, it's hard to say on the basis of the evidence you've presented thus far. But there are a few things that I'm having trouble reconciling on the basis of your code and comments:
You've declared detailViewController to be a DetailStaffMainContentViewController (and that's the class for which you've synthesized the teststring accessors);
But your error message suggests that detailViewController is clearly a MainSwitchBoardViewController object (which doesn't understand the setTeststring accessor method); and
You haven't shown us how you're setting detailViewController, so it's hard to say where you've gone wrong here.
Personally, in cases like this, I'm always reticent to define properties to maintain pointers to view controllers. I'd generally be inclined to ask the UISplitViewController what's in the detail split. So, rather than having a class property/ivar for that, in my master view controller, I have a method like:
- (UIViewController *)detailViewController
{
return [[self.splitViewController.viewControllers lastObject] topViewController];
}
That's basically saying "get the last object from my split view controller's array of viewControllers (the detail split) and because I (personally) always use a navigation controller in that detail split, let's use the topViewController to get a pointer to my subclassed UIViewController that's in that detail split (embedded within the navigation controller).
Then I have my didSelectRowAtIndexPath check to see if that view controller is the one I expected. If not, I segue to it (and have prepareSegue pass the data I want to that controller).
So, for example, consider this storyboard:
Here, "A" is my default detail controller. "B" might be some random scene that I may have replaced "A" with. And "C" is my real detail view controller where I can see the details for the item I selected from the master view controller's table view. Thus, my didSelectRowAtIndexPath for the master view controller might look like:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *controller = [self detailViewController];
if (![controller isKindOfClass:[DetailCViewController class]])
{
// if I'm not already showing "C" in my detail split, then let's segue to it
[self performSegueWithIdentifier:#"GoToC" sender:self];
}
else
{
// if I'm already showing "C" in my detail split, let's just set the item of data I want to pass to it
DetailCViewController *cController = (DetailCViewController *)controller;
cController.detailItem = self.objects[indexPath.row];
}
// I have a method I call to make sure that the popover menu for the master view
// controller disappears (in case it popped up since I was in portrait mode) and
// adds the master view controller button to the navigation bar if I need it.
[self removePopoverAndAddNavigationButton];
}
In case I needed to segue to "C", I also have my `prepareSegue pass the detailItem as needed:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"GoToC"])
{
DetailCViewController *cController = (DetailCViewController *)[segue.destinationViewController topViewController];
cController.detailItem = self.objects[[[self.tableView indexPathForSelectedRow] row]];
}
}
As I said, trying to figure out why your detailViewController has the wrong type of object is hard to say, but this is how I might tackle something like this.
Maybe you should first take a look at some nice movies from the last WWDC where they describe Storyboard Segues.
In the code you provided there is nothing written about segues. Be sure to use segues either via IB or programmatically via [self performSegueWithIdentifier:]. See Apple Docs
Then you can provide additional operations in yout view controller:
[view shouldPerformSegueWithIdentifier:sender:]
[view prepareForSegue:sender:]
The first one decides whether to perform the segue or not. The second one is executed just before segue is performed. Within the secon operation you can provide information to the destinationViewController.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"showIssuesByProject"]){
if (_searchActive) {
((IssueListViewController *)segue.destinationViewController).project = [_filteredProjects projectAtIndex:[self.tableView indexPathForSelectedRow].row];
} else {
((IssueListViewController *)segue.destinationViewController).project = [_projects projectAtIndex:[self.tableView indexPathForSelectedRow].row];
}
}
if ([segue.identifier isEqualToString:#"showIssuesByFilter"]) {
((IssueListViewController *)segue.destinationViewController).filter = [_filters filterAtIndex:[self.tableView indexPathForSelectedRow].row];
}
}
Be careful: UISplitViewControll.viewControllers are only two controllers: the master and detail ones.
I have a view controller
In storyboard, added tableview to view controller.
Created an IBOutlet for tableview to View controller's header file.
The view controller's header file includes an resultsarray
Changed the #interface to include delegates UITableViewDelegate, UITableViewDataSource
View controller implementation file has mandatory tableview protocols
(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
The view controller has search button in storyboard and IBAction in view controller header file. On click of search button, results are obtained. How do I load the array and redisplay tableview with results in search method. After I loaded the results in array, I tried [self viewdidload];, hoping the cells will be loaded. But didn't. I thought of calling
[self.detailView cellForRowAtIndexPath:?indexpath], but dontknow what the value of index path is. Appreciate help from guru's to load the UItableviewcell
Thanks
You should use reloadData or reloadSections methods of tableView in order to reload your tableView from data source.
// you fill your array with results here...
// and then call
[tableView reloadData];
This will call cellForRowAtIndexPath: methods for every cell to update its data.
Here is the reference.
Is it possible to have a separate XIB file for the NSTableCellView of a view-based NSTableView? Maybe with the help of a NSViewController?
Yes, it seems to be possible.
From Apple's documentation:
In order to function, a programmatically implemented view-based table must implement the following:
...
The - (NSView *)tableView:viewForTableColumn:row: method that is defined by the NSTableViewDelegate Protocol. This method both provides the table with the view to display as the cell for the specific column and row, as well as populates that cell with the appropriate data.
This way you can have an object of the class NSView (or any subclass) and give it back, after you properly filled it with data. Where you get this object from, is not of interest. As far as I know, it would be possible to do the following, for example:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
// Assume you have a XIB called View.xib
[NSBundle loadNibNamed:#"View" owner:self];
// And you have an IBOutlet to your NSTableView (that's view based) called tView
[tView reloadData];
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView {
return 20;
}
- (NSView *)tableView:(NSTableView *)tableView
viewForTableColumn:(NSTableColumn *)tableColumn
row:(NSInteger)row {
// Assume your class has an IBOutlet called contentOfTableView,
// your class is File's Owner of the View.xib and you connected the outlet.
return contentOfTableView;
}
Hope it works. I just threw it together having a rough idea in mind. Good luck!
In the master-detail application template (using ARC, storyboards) in XCode 4.3.2, I am trying to change (more specifically replace) the detail view when an item in master table view is selected. I am trying to implement delegates/protocols for this.
What I am confused about is - which class should implement the methods defined in protocol - master or detail?
Having the detail view implement the protocol method makes sense to me since, I'll be push/popping the view controllers in detail view based on the selection (passed as a string from master via the protocol method).
Here's what I tried
1) Defined the protocol in MasterViewController.h
#protocol MasterViewDelegate <NSObject>
- (void)masterSelectionChanged:(NSString *)selection;
#end
#interface MasterViewController:UIViewContoller
#property (nonatomic, weak) id <MasterViewDelegate> delegate
2) in MasterViewController.m
#synthesize delegate;
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[delegate masterSelectionChanged:#"Some string based on indexPath.row"];
}
3) in DetailViewController.h
#import "MasterViewController.m"
#interface DetailViewController:UINavigationController <MasterViewDelegate>
#end
4) in DetailViewController.m
#pragma mark - MasterViewDelegate
- (void)masterSelectionChanged:(NSString *)selection
{
NSLog(#"the selection is: %s", selection);
// WIll push/pop view over here, may be perform segues based on selection
}
In this process, upon selecting the rows in master table, nothing happened. No crash, no display of log, no error while building either. What did I miss over here?
You need to set the delegate property - at the moment it will be nil so nothing happens when you send messages to it. In the iPad template you can do this as follows, in the viewDidLoad of your detail view controller:
[super viewDidLoad];
if (self.splitViewController) // Means this won't be called if you use this code on iPhone too.
{
// According to comments your master controller is embedded in a nav controller
UINavigationController *nav = (UINavigationController*)[self.splitViewController.viewControllers objectAtIndex:0];
// I am assuming it is the root view controller
MasterViewController *master = (MasterViewController*)nav.rootViewController;
// Finally set the delegate
master.delegate = self;
}
I'm starting now with Xcode on 4.2 for iOS5 and there are a few changes and I'm now crossing a problem that I can't figure out a way to solve it.
I'm doing an example with a UITablwView that is populated programmatically with 2 Sections, 1st section with only 1 Row, and 2nd Section with 3 Rows.
My aim is to select a row from the table, and based on that row, the user will be redirected to different Views.
For example:
selecting section 0 row 0, app pushes to view 1 - name setting //
selecting section 1 row 0, app pushes to view 3 - address setting
The old fashion way, this is quite simple, just needed to init a UIViewController with initWithNibName and then push the view.
Now with the storyBoard everything changes, or at least I think it changes because I can't see how to get the same result since I can't set multiple segue's from the tableView to different UIViewControllers...and to do the old fashion way I can't see where I can get the NIB names from the views on the storyBoard to init an UIViewController to push.
Does any one knows how to get to this result??
Define two "generic" segues (identified as "segue1" and "segue2", for example) in the storyboard from your source view controller, one to each destination view controller. These segues won't be associated with any action.
Then, conditionally perform the segues in your UITableViewDelegate:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Conditionally perform segues, here is an example:
if (indexPath.row == 0)
{
[self performSegueWithIdentifier:#"segue1" sender:self];
}
else
{
[self performSegueWithIdentifier:#"segue2" sender:self];
}
}
I have the same problem as you do. The problem is that you can't link your tableViewCell to multiple view controllers. However you can link your source view itself to multiple view controllers.
Control-drag the master view controller (instead of table view cell) from the scene viewer to whatever view controller you want to link. You can do this as much as you want. Notice that the segue shown in source view controller scene should be something like "Push Segue from Root View Controller ..." instead of "Push Segue from NavCell to ...".
Identify each segue link a unique name like "toDetailView1"
Finally, custom the selection in your source view controllers:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2 == 1) {
[self performSegueWithIdentifier:#"toDetailView1" sender:self];
} else {
[self performSegueWithIdentifier:#"toDetailView2" sender:self];
}
}
Like #陳仁乾 and #Marco explained was completely correct. To make everything a little bit easier I would recommend you to use a single NSArray which will be initialized when viewDidLoad. Just name the segues the same as your UIViewControllers, this way you can Display a correct description of what UIViewControllers you can choose from and you can also perform the segues from this NSArray:
(Actually I'm not sure if it can cause any problems calling the segue the same as the UIViewController you want to call. Please let me know if this is BadPractise)
viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
_arraySessions = [[NSArray alloc] initWithObjects:
#"MyViewControllerName", nil];
}
cellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:#"overviewCell"
forIndexPath:indexPath];
[cell.textLabel setText:_arraySessions[indexPath.row]];
return cell;
}
didSelectRowAtIndexPath
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self performSegueWithIdentifier:_arraySessions[indexPath.row]
sender:self];
}