I want to change the selected row in the table (in MasterViewController) from a button on a detailViewController.
I know that I cannot call didSelectRowAtIndexPath because it is a delegate method. I tried using selectRowAtIndexPath but that does not invoke the didSelectRowAtIndexPath nor the willSelectRowAtIndexPath method. As suggsted here,
https://stackoverflow.com/a/7496926/1050722 it is possible to some new method and then call that method from the other ViewController (detailViewController), but how should that method look like?
Here is some code:
MasterViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self pushDetailViewController:indexPath];
}
-(void)pushDetailViewController:(NSIndexPath *)indexPath
{
if (!self.detailViewController) {
self.detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
}
[self.navigationController pushViewController:self.detailViewController animated:YES];
NSLog(#"pushDetailviewC and index %#", indexPath);
}
DetailViewController.m
-(IBAction)nextItem
{
MasterViewController *mVc = [[MasterViewController alloc] init];
[mVc pushDetailViewController:[NSIndexPath indexPathForRow:0 inSection:0]];
}
Can someone give me an example to fix this? Thanks.
EDIT: Here is the new code
MasterViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self pushDetailViewController:indexPath];
}
-(void)pushDetailViewController:(NSIndexPath *)indexPath{
if (!self.detailViewController) {
self.detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
}
self.detailViewController.mVc = self;
[self.navigationController pushViewController:self.detailViewController animated:YES];
NSLog(#"pushDetailviewC and index %#", indexPath);
}
DetailViewController.h
#import <UIKit/UIKit.h>
#import "MasterViewController.h"
#interface DetailViewController : UIViewController <UISplitViewControllerDelegate, UITableViewDelegate>
#property (strong, nonatomic) id detailItem;
#property (strong, nonatomic) IBOutlet UILabel *detailDescriptionLabel;
#property (nonatomic, retain) MasterViewController *mVc;
DetailViewCOntroller.m
-(IBAction)test{
//MasterViewController *mVc = [[MasterViewController alloc] init];
[self.mVc pushDetailViewController:[NSIndexPath indexPathForRow:0 inSection:0]];
}
It does not call the NSLog part of the method and it is not selecting the row...
This answer is iPad specific as per your question.
The iPad version of the Master/Detail template uses a UISplitViewController two split the screen as you see it. This UISplitViewController instance keeps references to both of your navigations controllers. Which keep reference to your topmost view controllers. Here is sample code, do not use it verbatim except to test since it contains no error checking.
// Picking some arbitrary row
NSUInteger desiredRow = 3;//???
// The Master navigation controller is at index zero in the template, you can check in your app delegate's didFinishLaunching: method
UINavigationController *masterController = [self.splitViewController.viewControllers objectAtIndex:0];
// Next we want a reference to the topmost viewController again you shouldn't assume it's a UITableViewController
UITableViewController *tableController = (UITableViewController *)masterController.visibleViewController;
// Next we get the reference to the tableview in-question
UITableView *tableView = tableController.tableView;
// We translate our desired row to an index path
NSIndexPath *path = [NSIndexPath indexPathForRow:desiredRow inSection:0];
// We select the row , again this will not call didSelectRowAtIndexPath:
[tableView selectRowAtIndexPath:path animated:YES scrollPosition:UITableViewScrollPositionNone];
// We call didSelectRowAtIndexPath: on the tableview's delegate
[tableView.delegate tableView:tableView didSelectRowAtIndexPath:path];
When I put this code in an IBAction method in the detail view controller the linked button performs the appropriate actions, both selects the row and pushes a VC, as if I had selected the row by touch.
In DetailViewController.h add a property to the object that points to the MasterViewController. You will also need to import the MasterViewController.h so something like
#import "MasterViewController"
...
#property (nonatomic, retain) MasterViewController *mVc;
...
Don't forget in DetailViewController.m to #synthesize mVc;
Now in your pushDetailViewController you add a reference to join the two objects
if (!self.detailViewController) {
self.detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil];
}
[self.detailViewController.mVc = self]; // New line
[self.navigationController pushViewController:self.detailViewController animated:YES];
Then in the DetailViewController you reference the object
-(IBAction)nextItem{
[self.mVc pushDetailViewController:[NSIndexPath indexPathForRow:0 inSection:0]];
}
I think another issue you were having was alloc'ing a new version of mVC each time you click the nextItem button. IOS will happily let you make lots of MasterViewController objects and makes a new one each time you alloc it. You wanted just to get a handle to your original MasterViewController.
Another approach would be to explore using the parent methods to reference the MasterViewController. But I wanted to show you how to do this with an explicit property since I thought it would be clearer.
Also, this may or may not fix all of your issues, but it hopefully at least shows you how to talk back to the actual MasterViewController.
Related
I have a UITableView with 10 cells in it. I have a UIButton & UILabel inside all the 10 cells. Now if I select a UIButton in the 2nd cell, I will navigate to another page and select some values (string values). Now, I want that string to be placed in the UILabel of 2nd cell. Likewise, if i select the UIButton on 5th cell, I will navigate to another page, select some string and that string has to be placed in the UILabel of the 5th cell. How do I insert the values inside the UILabel of that particular cell.
Ideas are appreciated.
I'd recommend setting that second view controller to contain an instance of the first so you can then manipulate the first view controller's data array from that second view.
For example, in the section of the FirstViewController where you create the SecondViewController to perform the push (since as you said in the comments, you're pushing the view controller using a UINavigationController), also make the SecondViewController contain an instance of the FirstViewController.
FirstViewController .m:
// Method to handle the push transition
- (void)buttonPressed:(UIButton*)button {
// Set the view controller's `selectedRow` property
// to contain the selected row number as contained in
// the button tag in this particular scenario
self.selectedRow = (int)button.tag;
// Create an instance of SecondViewController
SecondViewController *secondViewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
// Pass the view controller along by setting
// the secondViewController's firstViewController
// property to contain self, i.e. the current
// instance of the FirstViewController
secondViewController.firstViewController = self;
// Push to the SecondViewController
[self.navigationController pushViewController:secondViewController animated:YES];
}
And in the .h of SecondViewController, add the firstViewController property such that it's publicly accessible from the FirstViewController.
SecondViewController .h:
#import "FirstViewController.h"
#interface SecondViewController : UIViewController
#property (weak, nonatomic) FirstViewController *firstViewController;
Then in the .m of SecondViewController, you can manipulate the array acting as your table's data source in FirstViewController as needed, before popping the view.
SecondViewController .m:
// Method called after the string is selected
- (void)stringSelected:(NSString*)string {
// Update the array to contain the string
// (or do whatever manipulation to whatever data
// structure is fitting in your particular case)
[self.firstViewController.array replaceObjectAtIndex:self.firstViewController.selectedRow withObject:string];
// To go back to the previous view, pop the view controller
[self.navigationController popViewControllerAnimated:YES];
}
Also, you'll want the UITableView's data source array and selectedRow property to be contained in the .h of FirstViewController so it's publicly accessible from SecondViewController.
FirstViewController .h:
#property (weak, nonatomic) NSMutableArray *array;
#property (nonatomic) int selectedRow;
And in the .m of FirstViewController you want to make sure to reload the table data upon return from the other view so the UITableView reflects the updated data.
FirstViewController .m:
- (void)viewWillAppear:(BOOL)animated {
[self.table reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CellName" forIndexPath:indexPath];
if(cell == nil)
{
cell = [[CustomTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"CellName"];
}
//Assigning the contents of cell
cell.labelName.text = [NSString stringWithFormat:#"%#", [self.arrCategoryTitle objectAtIndex:indexPath.row]];
[cell.buttonName addTarget:self action:#selector(buttonNamePressed:) forControlEvents:UIControlEventTouchDown];
// Configure the cell...
return cell;
}
- (void)buttonNamePressed:(UIButton *)sender
{
CGPoint swipepoint = sender.center;
CGPoint rootViewPoint = [sender.superview convertPoint:swipepoint toView:self.tableName];
NSIndexPath *indexPath = [self.tableName indexPathForRowAtPoint:rootViewPoint];
NSLog(#"%#", indexPath);
[self.tableName cellForRowAtIndexPath:indexPath];
self.defaultRowId = indexPath.row;
[self performSegueWithIdentifier:#"viewControllerIdentifierName" sender:self];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
DetailsViewController *detailsViewController = (DetailsViewController *)[(UINavigationController *)segue.destinationViewController topViewController];
detailsViewController.defaultrow = self.defaultRowId;
}
Now when you navigate to the secondViewController, set the selecting string value to the property of the firstViewController then reload table.
I have a ViewController1.h/m and SettingsViewController.h/m. In ViewController1.m I have this:
ViewController1.h
#property (nonatomic, weak) UIView *settingsView;
ViewController1.m
//alloc init _settingsView
SVC = [[SettingsViewController alloc]init];
[self addChildViewController:SVC];
SVC.view.frame = self.view.bounds;
[_settingsView addSubview:SVC.view];
[SVC didMoveToParentViewController:self];
I don't remove ViewController1 from parent view when I add the settings. It's just off to the side and slides in when I'm toggling the settings view with a button. Inside SettingsViewController I have a table and on a row click I want to slide the SettingsViewController back off the screen and perform a selector from ViewController1...that's where my problem is right now. Here's what I try to do:
SettingsViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
if (indexPath.section == 0) {
ViewController1 *VC1 = [[ViewController1 alloc]init];
[VC1.settingsView setFrame:CGRectMake(320, 0, VC1.view.frame.size.width, VC1.view.frame.size.height)];
//NSLog(#"%f", VC1.settingsView.center.x);
//tried:
//[VC1 performSelector:#selector(...)]; I have the method declared in VC1.h
}
}
Logging the VC1.settingsView.center.x always gives me 0...when it should be 160. Adding a log statement in VC1 methods when running performSelector DOES log but the rest of the method doesn't run, doesn't slide the SettingsViewController off the screen. Am I not understanding parent/child relationship???
Crap, found the answer after hours of trying...thanks to this
if([self.parentViewController isKindOfClass:[SomeViewController class]]) {
SomeViewController* viewController = (SomeViewController*)self.parentViewController;
[viewController foo];
}
objective-c and ios newbie here but I'm learning!
name is a string in descs. I'm trying to pass it to a new view controller through detailViewController.note. I can see in debugging that the string is being saved in detailViewController.note ,but when I go over to my other view controller it is not there.
here is the code from my table view controller:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:NO];
DetailViewController *detailViewController = [[DetailViewController alloc]init];
detailViewController.view.frame=self.view.frame;
detailViewController.title=#"detail";
detailViewController.note=[self.descs[indexPath.row]name];
[[self navigationController] pushViewController:detailViewController animated:YES];
}
Header for DetailViewController:
#interface DetailViewController : UIViewController
#property (nonatomic) NSString * note;
#end
Implementation for DetailViewController:
- (void)viewDidLoad
{
[super viewDidLoad];
UITextView *description =[[UITextView alloc]initWithFrame:CGRectMake(12,260,300,180)];
description.font=[UIFont fontWithName:#"Helvetica" size:15];
description.editable=NO;
description.text=self.note;
[self.view addSubview:description];
}
Thanks in advance for your help!
You should set up your subview in viewWillAppear:. It's not working in your current implementation because viewDidLoad is called before detailViewController.note has been set.
I have followed the tutorial Passing Data between View Controllers section Passing Data Forward. My code is:
MasterViewController.h:
-(void)pushViewController: (UIViewController *)detailVC animated:(BOOL)animated;
MasterViewController.m:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell=[tableView cellForRowAtIndexPath:indexPath];
NSInteger num = indexPath.row;
DetailViewController *detailVC = [[DetailViewController alloc] initWithNumber:indexPath.row];
detailVC.number = num;
[self pushViewController:detailVC animated:YES];
}
I want to pass the integer num to my DetailViewController. But I am getting Thread 1: SIGABRT. What is happening?
MasterViewController doesn't inherit from UINavigationController.
You can't push to the navigation stack if there isn't one.
You can either add one, or present the new VC, depending upon what the design of your GUI is intended to be.
P.S.
WHat's the point in setting the number twice as you are:
NSInteger num = indexPath.row;
DetailViewController *detailVC = [[DetailViewController alloc] initWithNumber:indexPath.row];
detailVC.number = num;
You either set it with init or set it directly, you're doing it both ways
My best guess (without seeing your #interface for DetailViewController) is that it's simply a matter of the detailVC instance going away after you put it on screen.
Simple solution: use an instance variable instead:
#interface WhateverClassYouAreIn
#property (nonatomic, strong) DetailViewController *detailVC
#end
// ...
self.detailVC = [[DetailViewController alloc] initWithNumber:indexPath.row];
The reason this is happening is that without the instance variable, the storage duration of the detailVC is the scope of the method in which it is declared. In general, any VC that goes on screen should be held 'strongly' by something else, all the way back to the root vc.
Is the method "initWithNumber" defined in DetailViewController.h ?
#interface DetailViewController : UIViewController
- (instancetype)initWithNumber:(NSInteger)number;
#end
I have a Splitview with a Tabbar with 5 buttons, in the left side.
Every tab has a navigation controller with a tableview.
Some premises:
App is not built with storyboard
I do not understand the architecture of the Apple sample code "MultipleDetailViews.xcodeproj"
Button 5, header file.
#import <UIKit/UIKit.h>
#class DetailViewiPad;
#interface TAB5 : UIViewController <UITableViewDataSource, UITableViewDelegate>
{
DetailViewiPad *detailViewController;
}
#property (nonatomic, strong) IBOutlet DetailViewiPad *detailViewController;
#end
Button 5, implement file.
#implementation TAB5
#synthesize detailViewController;
[some code...]
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if ([key isEqualToString:#"A"]) {
[detailViewController setDetailItem:[websites objectAtIndex:row]];
} else if ([key isEqualToString:#"B"]) {
TableView2 *controller = [[TableView2 alloc] initWithNibName:#"TableView2" bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
}
Option "A" WORKS PERFECT, the Detail View (right side) shows its content!
If you select Option "B" (you navigate to TableView2) and then you select some row:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DetailViewiPad *detailViewController =
[[DetailViewiPad alloc] initWithNibName:#"DetailViewiPad" bundle:nil];
[detailViewController setDetailItem:[websites objectAtIndex:row]];
DETAIL VIEW NOT RESPOND. If you set a breakpoint you can see that program flow goes to detail view and it passes data but you can not see updates in the view (iPad device).
Repeat: program flow goes to detail view and it passes data but not refresh the screen.
It seems like some problem of connection between new class (TableView2.h, TableView2.m, TableView2.xib) and the detail view. Some kind of delegate problem.
I found it !!!!
Fantastic !!!
Premise: my detail view it calls DetailViewiPad
DetailViewiPad.h
DetailViewiPad.m
DetailViewiPad.xib
That is the key, the magical code:
DetailViewiPad *detailViewController = (DetailViewiPad*)self.splitViewController.delegate;
[detailViewController setDetailItem:[websites objectAtIndex:row]];
It is INCORRECT the sentence:
DetailViewiPad *detailViewController = [[DetailViewiPad alloc] initWithNibName:#"DetailViewiPad" bundle:nil];
The key to obtain the delegation from subsequents Tableviews to detail View (in iPad Splitview) is:
DetailViewiPad *detailViewController = (DetailViewiPad*)self.splitViewController.delegate;