Implementing delegation between views embedded in two separate container controllers - ios

The relevant part of my storyboard appears as follows:
You can see the custom "Container Controller" view houses two Container Views, one which links to a Navigation Controller via embedded segue, and another that links to a custom "Master View Controller" (which implements a Table View Controller) via embedded segue. The Navigation Controller component further has a relationship with a custom "Location Filter Controller."
I need to implement delegation such that when one of the UISteppers in the Location Filter Controller is incr./decr., the table view in the Master View Controller knows to update the data it displays accordingly.
I am not unaccustomed to working with protocols/delegates, but this unique situation of talking between views housed in segues is really tricking me! For the most part I have had success following the example here: Passing Data between View Controllers. In this case however, I am not able to directly link instantiated views as he indicates to do in 'Passing Data Back' step 6.
I had considered using a singleton object from which each of these views could get/set the necessary data, but the issue here is that the table view would not necessarily know when to update its contents, despite having data with which it could/should update.
Here is a code snippet from ContainerController.m where I setup the embedded segues to function:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
DataHold *data = [[DataHold alloc] init]; // <-- this actually is a singleton object
if([segue.identifier isEqualToString:#"locationEmbedSegue"])
{
}
else if([segue.identifier isEqualToString:#"tableEmbedSegue"])
{
[[segue destinationViewController] setDelegate:data.detailTableViewController];
// ^ This part actually sets up a delegate so that the table view (Master View Controller)
// delegates to the detail view controller of the overarching split view controller
// and tells it what to display when a row is pressed.
}
}
Thanks for any help!

I think you are on the right track setting the table view delegate to your Location Filter Controller.
I found that a simple way to work with embeded view controller is to add "placeholders" property for them, and set these property when the segue is "performed".
// MyContainerController.h
#property (strong, nonatomic) MyLocationFilterController *detailViewController;
#property (strong, nonatomic) UITableViewController *masterViewController;
// MyContainerController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"locationEmbedSegue"])
{
UINavigationViewController *dest = (UINavigationViewController *)segue.destinationViewController;
self.detailViewController = dest.topViewController;
}
else if([segue.identifier isEqualToString:#"tableEmbedSegue"])
{
self.masterViewController = (UITableViewController *)segue.destinationViewController;
[self.masterViewController.tableView setDelegate:self.detailViewController];
}
}

I came to this question recently and found there may be one problem with the answer above.
Move the setDelete: method out. This makes sure no controller is nil.
Then code becomes:
// MyContainerController.h
#property (strong, nonatomic) MyLocationFilterController *detailViewController;
#property (strong, nonatomic) UITableViewController *masterViewController;
// MyContainerController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if([segue.identifier isEqualToString:#"locationEmbedSegue"])
{
UINavigationViewController *dest = (UINavigationViewController *)segue.destinationViewController;
self.detailViewController = dest.topViewController;
} else if([segue.identifier isEqualToString:#"tableEmbedSegue"])
{
self.masterViewController = (UITableViewController *)segue.destinationViewController;
}
[self.masterViewController.tableView setDelegate:self.detailViewController];
}

Related

Protocols with Navigation Controller

I have a project where I have set up a protocol to pass information back from one TableViewController to a ViewController. Everything worked fine and as expected, but I decided to embed in a Navigation Controller to the TableViewController so I could add a "DONE" barButtonItem to dismiss the Controller when the user is done. Since embedding in the navigation controller, the button works well, the TablieViewController looks identical, but none of its features and methods that use the Protocol and Delegate work, and if I remove the NavigationController everything works. Could someone explain how I can fix this issue? I am fairly new to iOS and objective c.
Here is the prepareForSegue method in the NoteViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.destinationViewController isKindOfClass:[ToolTableViewController class]]) {
ToolTableViewController *targetVC = segue.destinationViewController;
targetVC.toolDelegate = self;
targetVC.autoCorrectIsOn = self.autoCorrectIsOn;
targetVC.undoAvailable = self.undoAvailable;
targetVC.redoAvailable = self.redoAvailable;
}
}
ToolTableViewController.h
#protocol ToolTableViewControllerDelegate <NSObject>
#property (weak, nonatomic) id <ToolTableViewControllerDelegate> toolDelegate;
ToolTableViewController.m - example of a method called
-(void)clearInputText{
// NSLog(#"Clear Method Selected");
[self.toolDelegate didClearInputText];
}
NoteViewController.m
-(void)didClearInputText{
self.noteTextView.text = #"";
[self dismissViewControllerAnimated:YES completion:nil];
}
Since your table view controller is embedded in a navigation controller, it's the navigation controller that will be the destination view controller of the segue. Also, it would be better to use the identifier of the segue for the if statement, rather than the class of the destination view controller (I'm using "SegueToTable" as the identifier, change that to whatever you put for the identifier). Therefore, prepareForSegue should look like this,
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:#"SegueToTable"]) {
UINavigationController *nav = segue.destinationViewController;
ToolTableViewController *targetVC = nav.topViewController;
targetVC.toolDelegate = self;
targetVC.autoCorrectIsOn = self.autoCorrectIsOn;
targetVC.undoAvailable = self.undoAvailable;
targetVC.redoAvailable = self.redoAvailable;
}
}
Your delegate methods are called just fine (based on the sample you pasted).
Since your controllers are embedded in a navigation controller now, you should use:
[self.navigationController popViewControllerAnimated:YES]
Before, you were presenting your controllers modally, that's why dismissViewController worked fine then but not now ( in the context of a nav controller).

ViewController's private data set to nil after returning from segue

I have two views. The main view is ViewController and the next is AddItemViewController. ViewController has a tableview that lists items that you add when you go to AddItemViewController. There is a button on AddItemViewController that segues back to ViewController. The problem is, upon returning to ViewController expecting that an item be added, the private data of ViewController is suddenly set to nil. I have lost data and any chance to interact with my objects after returning from the segue.
Here is the data that's getting set to nil
#property (strong, nonatomic) costEstimator *myCost;
#property NSString *testString;
What am I doing wrong?
Here is my prepareforsegue code in the AddItemViewController
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
// Get the new view controller using [segue destinationViewController].
ViewController *vC = [segue destinationViewController];
[vC addSomething:_selectedItem withQuantity:[_quantBox.text doubleValue]];
}
Any help is greatly appreciated. Thanks :)
You want to pop the AddItemViewController in this case. When you segued from ViewController to AddItemViewController, I am guessing you did a push. What this does effectively is it adds AddItemViewController ontop of ViewController in the memory stack. By 'segue-ing' again from AddItemViewController to ViewController, you are adding ANOTHER ViewController instance ONTOP of AddItemViewController. This is why you think you are losing your data when in actuality, you aren't. You are only seeing the wrong view controller.

Segue to a UITableViewController with custom init

I have a simple push segue that goes from one UITableViewController to another. At the time of initing the other UITableViewController, I want to pass an id that will be used to perform an sqlite query and populate the table. I could use a public property and reload the table in a custom setter, but the design seems better if it is done on initing. After searching around for quite a while I haven't been able to find any clear examples. I have a seen a mentions of initWithCoder and initWithFrame but they haven't been clear enough to connect it to what I am trying to do.
Here is what the segue looks like from the first table:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ShowFlashcards"]) {
//pass variable to custom init somewhere around here?
FlashcardsTableViewController *flashcardsTableViewController = [segue destinationViewController];
}
}
Here is what the custom init looks like:
#property (nonatomic, strong) NSNumber *listId;
#property (nonatomic, strong) NSArray *flashcards;
- (id)initWithListId:(NSNumber *)listId {
self = [super init];
if (self ) {
self.listId = listId;
Database *db = [[Database alloc] init];
self.flashcards = [db getWordsFromList:[listId integerValue]];
}
return self;
}
You are over-thinking this, and coming to some wrong conclusions.
Adding a property to the destination VC (view controller) and setting it in prepareForSegue is exactly what you want to do.
You can't use a custom init in this situation. Invoking the segue allocates and initializes the destination VC (actually, I believe it is created using the initWithCoder method.) The destination VC's view won't have been loaded yet when prepareForSegue is called, so you can set properties and they will be set up by the time the destination VC's viewDidLoad method gets called.
#AJHacker's answer shows how to give the destination VC a pointer to the source VC, which is useful when the destination needs to be able to send data back.
You would need to add a property that contains a reference of the first view into the second view. For example:
#interface AutoRenewDrop
#property(weak, nonatomic) AddR *callerView;
#end
And then in the 'done' method of the second view you can just update the variables in the caller view like so:
-(IBAction)done:(UIStoryboardSegue *)segue {
[self.navigationController popViewControllerAnimated:YES];
callerView.renewDate.text = transferData;
}
Of course when you initiate the second view you will have to set the reference, like this:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"Check Mark Segue"])
{
NSLog(#"Transfering Data");
AutoRenewDrop *controller = segue.destinationViewController;
controller.transferData = self.renewDate.text;
controller.callerView = self; //Here, you are passing the reference to this View
}
}
Hope that helps

iOS - navigation controller to tab bar controller

In my storyboard, I have a Table View Controller and I want to be able to tap on an item in the table view and go to a detail view with more information. In the detail view, I'd like it to be like the Tab Bar Controller as the detail view can be separated into 2 categories.
Is this possible? At the moment I can do it in my storyboard, but after searching around, I can't seem to find out how to pass the details of the selected item from the Table View Controller to the Tab Bar controller.
Am I missing something obvious? Or going about this wrong? I'm new to iOS development so any advice would be appreciated.
Thanks!
Have you tried passing the information to one of the TabBarController's view controllers in a property through the prepareForSegue method?
Create two properties in the TableView Controller:
#property (strong, nonatomic) UITabBarController *tabBarController;
#property (strong, nonatomic) YourViewController *firstViewController;
Then Use:
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"prepareForSegue: %#", segue.identifier);
if ([segue.identifier isEqualToString:#"mySegueID"]) {
self.tabBarController = (UITabBarController*) [segue destinationViewController];
self.firstViewController = [self.tabBarController.viewControllers objectAtIndex:0];
self.firstViewController.SOMEPROPERTY = SOMEVALUE;
}
}

Connecting data in MVC

I have a Navigation Controller and 2 Views Controllers. The first View Controller is associated with a UIViewController named ViewController. The second is connected to a UIViewController named BookVC. BookVC has a UITextField and is connected via an outlet:
#property (strong, nonatomic) IBOutlet UITextField *textFieldContent;
The connection is made with a button that uses a segue. I want to pass some data between the two and am using the following code which fails:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender{
BookVC* nextPage = [[BookVC alloc] init];
nextPage = [segue destinationViewController];
nextPage.textFieldContent.text=#"Some content";
}
How should I pass data between the View Controllers?
I think the problem is that textFieldContent doesn't exist at that point. You'll need to add a property to BookVC that can hold the text you want to put into textFieldContent... we'll call it #property NSString *textFieldContentText. So then,
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
BookVC *nextPage = [segue destinationViewController];
[nextPage setTextFieldContentText:#"Some content"];
...
}
And then, in the -viewDidLoad method of BookVC:
- (void)viewDidLoad
{
[textFieldContent setText:[self textFieldContentText]];
}
You don't need this line:
BookVC* nextPage = [[BookVC alloc] init];
That creates a brand new BookVC which you then go and overwrite in the next line.
Since you are using [segue destinationViewController] you should be good to go. What's happening when you segue to nextPage?

Resources