I have a tableview and a detailview, connected with segues.
I want the detailview's title to be the title of the selected cell.
If I use this code in GradeDetail.m, it works, but is static, of course:
self.navigationController.title = #"Title goes here";
If I use this on the other hand, it doesn't work, but should be dynamic:
self.navigationItem.title = grade.fag;
I have the class Grade imported where necessary and "fag" (means "school subject" in Danish, not gay) property defined in the class.
Here is an excerpt of the code from the tableview:
...
- (id)initWithCoder:(NSCoder *)aDecoder
{
if ((self = [super initWithCoder:aDecoder]))
{
grades = [[NSMutableArray alloc] init];
Grade* grade = [[Grade alloc] init];
grade.fag = #"Dansk";
grade.mundtlig = #"7";
[grades addObject:grade];
...
}
return self;
}
And the code pushing the detail-view:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
GradeDetail *detailViewController = [[GradeDetail alloc] init];
Grade* grade = [self.grades objectAtIndex:indexPath.row];
detailViewController.grade = grade;
//[self.navigationController pushViewController:detailViewController animated:YES];
}
Notice from the above code, what I commented out:
[self.navigationController pushViewController:detailViewController animated:YES];
If I remove the segue in the storyboard, and use this line of code instead, the detail-view shows the correct title, but the view isn't loading correctly. (Read: I don't want to use this line of code, I want it to work in storyboard.)
Please tell me if you want to see more of the code or have some of the above clarified.
You'll need to override -prepareForSegue:sender: to get a hook into the incoming view controller and set any properties required. You'll also need to specify segue identifiers in the storyboard.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ShowGradeDetails"])
{
GradeDetail *detailViewController = (GradeDetail *)segue.destinationViewController;
NSIndexPath *selectedIndexPath = [self.tableView indexPathForSelectedRow];
Grade *selectedGrade = [self.grades objectAtIndex:selectedIndexPath.row];
detailViewController.grade = selectedGrade;
}
}
Maybe try setting the viewController's title: self.title = grade.fag;
Related
I've a table view with some rows and each row has its Detail Disclosure button.
I want that when the user taps on that button, another table view (customized by storyboard) appears and shows some relative data.
From TableViewController.m
-(void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
DetailViewController *detailViewController = [[detailViewController alloc] init];
//creating a parse object
PFObject *checkin = [_dataSourceArray objectAtIndex:indexPath.row];
//getting data i want
NSString *trainNumb = [checkin objectForKey:#"trainNumber"];
//passing the data
detailViewController.trainNumber = trainNumb;
}
From the DetailTableViewController.m
- (void)viewDidLoad {
[_trainNumberLabel setText:_trainNumber];
}
The problem is that in the DetailTableView the NSString results to be null.
What I'm missing here? Thank you!
If this code actually compiles, this may be your problem:
DetailViewController *detailViewController = [[detailViewController alloc] init];
It should be:
DetailViewController *detailViewController = [[DetailViewController alloc] init];
But you shouldn't be alloc/initing your view controller in the first place. When you customize a view controller in Interface Builder, you've got to instantiate it like this if you want those customizations:
[self.storyboard instantiateViewControllerWithIdentifier:#"MyStoryboardIdentifier"]`
And you've got to set the controller's storyboard ID to "MyStoryboardIdentifier" or whatever identifier you want to use.
Also, as you've indicated in the comments, you've got a timing issue: your detail controller's viewDidLoad runs before you set the train number. A better approach would be to ensure that it works regardless of the sequence:
- (void)updateTrainNumberLabel
{
self.trainNumberLabel.text = self.trainNumber;
}
- (void)setTrainNumber:(NSString *)trainNumber
{
_trainNumber = trainNumber;
[self updateTrainNumberLabel];
}
- (void)viewDidLoad
{
...
[self updateTrainNumberLabel];
}
In other words, you configure your label in a separate method, in this case updateTrainNumberLabel, and when anything that could affect the label happens, e.g. the view loading or the number being changed, you call the method.
I'm new to storyboards and I'm having a problem with custom initialization.
Before using storyboards I used to do this to push a view controller from a table which has a custom initialization:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
//Creating Dummy Hotel
HotelItem *hotel = [[HotelItem alloc] init];
HotelDetailViewController *controller = [HotelDetailViewController controllerWithHotel:hotel];
controller.hidesBottomBarWhenPushed = YES;
[self.navigationController pushViewController:controller animated:YES];
[hotel release];
}
Now with storyboards I'm using prepareForSegue instead of didSelectRowAtIndexPath which turned into this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
//Creating Dummy Hotel
HotelItem *hotel = [[HotelItem alloc] init];
hotel.name = #"Hotelucho";
// Make sure we're referring to the correct segue
if ([[segue identifier] isEqualToString:#"ShowSelectedHotel"]) {
HotelDetailViewController *controller = [segue destinationViewController];
}
}
I know that I can change my private variable "hotel" and set the property right after the [segue destinationViewController] but I find more useful to call my custom init method.
Is there a way to achieve this?
IF you've got a big chunk of code you need to do after an init, you might want to create a separate method for it, and then call that method in initWithCoder: and initWithFrame:.
Check this answer for more info:
Objective C - Custom view and implementing init method?
How to pass values from one controller to another??? I use a StoryBoard.
I would like this to appear on the highlighted text view of the first view.
Call the next view of the code, I think something like this should look like:
UIStoryboard *finish = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *viewController = [finish instantiateViewControllerWithIdentifier:#"FinishController"];
viewController.modalPresentationStyle = UIModalPresentationPageSheet;
[self presentModalViewController:viewController animated:YES];
finishcontroller:
- (void)viewDidLoad
{
self.lblFinishTitle.text=self.FinishTitle;
self.lblFinishDesc.text = self.FinishDesc;
self.lblFinishPoint.text=self.FinishPoint;
[super viewDidLoad];
// Do any additional setup after loading the view.
}
first view:
-(void) prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender
{
if ([segue.identifier hasPrefix:#"FinishController"]) {
FinishController *asker = (FinishController *) segue.destinationViewController;
asker.FinishDesc = #"What do you want your label to say?";
asker.FinishTitle = #"Label text";
asker.FinishPoint = #"asdas";
}
}
I want to pass a value causing the transmission of the code
The issue is that you aren't actually using that segue, you are using presentModalController instead.
Note that usually, you can just ask self for it's storyboard. However, even that is unnecessary when you have a segues connected:
[self preformSegueWithIdentifier:#"FinishController" sender:self];
Then prepareForSegue will be called. Also note that you can (should) use something more authoritative than the segue identifier to determine if you should load the data... you can ask the segue's destination controller if it is the right class:
-(void) prepareForSegue:(UIStoryboardPopoverSegue *)segue sender:(id)sender
{
if ([segue.destinationViewController isKindOfClass:[FinishController class]]) {
FinishController *asker = (FinishController *) segue.destinationViewController;
asker.FinishDesc = #"What do you want your label to say?";
asker.FinishTitle = #"Label text";
asker.FinishPoint = #"asdas";
}
}
You are probably already aware (since you used the identifier in your code), but for the benefit of future discoverers of this post; segues are given identifiers in the inspector panel of Xcode when you are in the storyboard.
Basically when I implement a split view like the one presented in the Apple example 'MultipleDetailsViews', everything works fine, it allocates a new detailed view each time a row is selected. Here is the relevant code from the example:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (row == 0) {
FirstDetailViewController *newDetailViewController = [[FirstDetailViewController alloc] initWithNibName:#"FirstDetailView" bundle:nil];
detailViewController = newDetailViewController;
}
// ...
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
// ...
}
But what I'm looking for is reusing detailed view controllers, that is lazily allocating a view controller when it is selected, and keeping a reference on it in my object. That way when another row is selected the view controller is not deallocated and when it is selected again it would be reused instead of allocating a new one. Here is the relevant code:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIViewController <SubstitutableDetailViewController> *detailViewController = nil;
if (row == 0) {
if (self.firstDetailViewController == nil) {
FirstDetailViewController *newDetailViewController = [[FirstDetailViewController alloc] initWithNibName:#"FirstDetailView" bundle:nil];
self.firstDetailViewController = newDetailViewController;
[newDetailViewController release];
}
detailViewController = self.firstDetailViewController;
}
// ...
NSArray *viewControllers = [[NSArray alloc] initWithObjects:self.navigationController, detailViewController, nil];
splitViewController.viewControllers = viewControllers;
// ...
}
self.firstDetailViewController is instanciated the first time the first row is selected then it is reused.
When I'm doing that it works well in landscape mode but in portrait mode after a few clicks in the popover menu it raises an exception: *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Popovers cannot be presented from a view which does not have a window.'
So why I care? and why I don't want to reallocate view controllers? because in some of them I want to perform tasks that wouldn't be interrupted (killed) if the user navigate in a new detailed view while the task isn't completed yet.
Is there someone with an idea of what happens or with a working implementation of what I'm trying to achieve?
The view controllers are designed to be created and thrown away, if you need something to run in the background for longer then best move it into the master view controller or a separate object.
If you do however want to experiment with reusing a view controller this can be achieved by setting the viewControllers property on the new navigation controller with the previous detail controller saved in the viewDidLoad:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"showDetail"]) {
NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
NSManagedObject *object = [[self fetchedResultsController] objectAtIndexPath:indexPath];
UINavigationController* navigationController = (UINavigationController*)[segue destinationViewController];
// reuse existing controller
navigationController.viewControllers = #[self.detailViewController];
// update the detail controller as normal.
[controller setDetailItem:object];
controller.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem;
controller.navigationItem.leftItemsSupplementBackButton = YES;
}
}
i got a tableview controller.
if a cell is selected i perform the following:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
ablogSingleCatTableViewController *singleCatTableViewController = [[ablogSingleCatTableViewController alloc] initWithStyle:UITableViewStylePlain];
// Push the detail view controller.
[[self navigationController] pushViewController:singleCatTableViewController animated:YES];
[singleCatTableViewController release];
}
i want to commit an object to the next view controller that slides in after this row is selected.
do i have to do something like this?
ablogSingleCatTableViewController *singleCatTableViewController = [[ablogSingleCatTableViewController alloc] initWithStyle:UITableViewStylePlain];
[singleTableViewController setMyObject:superDuperObject];
or is there an easier way to do something like that?
i need this object directly after this tableviewcontroller is initialized, to fill it with specific data that belongs to this object.
please give me some advices.
thanks
You can also pass the object to the next view controller when you init the view controller.
To do so, you need to implement your own initializer for the view controller.
For example:
- (id)initWithStyle:(UITableViewStyle)style superDuper:(SuperDuper*)superDuperObject {
if (self = [super initWithStyle:style]) {
superDuper = superDuperObject;
}
return self;
}
Then,
ablogSingleCatTableViewController *singleCatTableViewController =
[[ablogSingleCatTableViewController alloc] initWithStyle:UITableViewStylePlain superDuper:superDuperObject];