Unable to pass delegate object to another class (Modal View) via IBAction - ios

I have a UIBarButtonItem on my UINavigationBar named selectRoleButton. Within this class (AEFeedsViewController) I have a protocol:
#protocol AERoleSelectProtocol <NSObject>
- (void)presentLoginViewController;
#end
The selectRoleButton is hooked up to AERoleSelectViewController by a target action IBAction, which holds a container that contains AERoleSelectTableViewController.
Here is a picture of my Storyboard
Below is the IBAction for selectRoleButton:
- (IBAction)selectRole:(UIBarButtonItem *)sender
{
NSLog(#"Sender: %#", sender); // Unable to breakpoint, NSLog won't log...?
AERoleSelectTableViewController *roleViewControler = [self.storyboard instantiateViewControllerWithIdentifier:#"RoleTableVC"];
roleViewControler.delegate = self; // delegate is not being passed/set
[self presentViewController:roleViewControler animated:YES completion:nil];
}
So when tapping the button, it presents AERoleSelectViewController, with AERoleSelectTableViewController in a container. AERoleSelectTableViewController has a delegate property of
AERoleSelectTableViewController.h
#property (weak, nonatomic) id <AERoleSelectProtocol> delegate;
The IBAction should be passing and setting the delegate object, but it is nil. Breakpoints do not seem to work, it won't break at all on the method. I've even tried writing my own method and wiring the button to that, but the breakpoint still won't catch it. I also cannot print anything using NSLog method for some reason.
In AERoleSelectTableViewController.m, my didSelectRowAtIndexPath: has a switch statement, one case uses the delegate and calls a method on AEFeedsViewController which conforms to the AERoleSelectProtocol.
AERoleSelectTableViewController.m
...
if (self.delegate) {
[self.delegate presentLoginViewController];
}
But since the delegate is nil, it obviously never gets called.
Edit:
I have tried disabling the target action IBAction on the selectRole button and using a segue to present it modally.
My segue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"showRoleView"])
{
NSLog(#"Sender: %#", sender);
AERoleSelectTableViewController *roleViewController = (AERoleSelectTableViewController *)[segue destinationViewController];
roleViewController.delegate = self;
}
}
The method is being called, I can breakpoint and it is crashing on
roleViewController.delegate = self;
With the crash log:
[AERoleSelectViewController setDelegate:]: unrecognized selector sent to instance
As mentioned above, I have a delegate property on AERoleSelectTableViewController.h so it should be passing no problem. But it crashes instantly, viewDidLoad doesn't even get called on AERoleSelectTableViewController.
Is this something to do with how I'm setting it on a class within a container? I tried setting the delegate on the container class (AERoleSelectTableViewController) on [segue destinationViewController] and when viewDidLoad gets called, the delegate object is set, but only the container view is displayed (the just the tableView, no background etc, RoleSelectViewController doesn't present at all, which is expected given this code.
When setting the[segue destinationViewController] to the class which holds the container (RoleSelectViewController), the containers' class viewDidLoad is called first. So the delegate is not set. Then viewDidLoad gets called on the class holding the container, with the delegate set successfully. Am I able to pass the delegate from the class which holds the container, to the container even though the container gets called first? Is using a container even the correct approach? Is a container needed or can I just put a tableView straight in there?

Related

Passing a PFObject between views is causing values to be null

I'm new to Parse and iOS app development, so please excuse my question if it has an obvious answer.
In my app, the user needs to enter data across multiple views, and for resource efficiency, I am initiating the PFObject as a property in the first view and it is being handed via prepareForSegue to by each scene to its segue's destination view controller.
However, when checking the key-value pairs in the object, I noticed that they are not getting stored in the object. In the debugger, it shows the data in the "estimatedData" section. What is the cause of this? When I try to saveInBackground the object, it fails and says that the object is null.
Here is the code from the FirstViewController.h of the PFObject property declaration.
#property (strong, nonatomic) PFObject *freshChow;
I also call #synthesize freshChow; under the #implementation of the FirstViewController.m.
I later initialize the object in an IBAction when a button is tapped.
- (IBAction)StartCookingProcess:(id)sender {
freshChow = [PFObject objectWithClassName:#"FoodItems"];
[self performSegueWithIdentifier:#"Perform Init Segue" sender:self];
}
And the prepareForSegue method:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([[segue identifier] isEqualToString:#"Start Cooking Process"]) {
Chow_Type_selection *vc = [segue destinationViewController];
vc.freshChow = freshChow;
}
}
This code, with the exception of the StartCookingProcess method is repeated on the subsequent views.
Thanks,
Siddharth

Passing NSArray makes array empty

i'm trying to pass an array from a view to another using the PrepareForSegue method.
In the first view i got a button called "Submit" that, if pressed, reads a textView and store the text in a NSArray, and then should pass this array to another view (push segue), but when the array arrives is empty.
Here is the code
//.h
#property (strong, nonatomic) NSArray *words;
//.m
- (IBAction)Submit:(id)sender{
//read textView
_words = [self.myTextView.text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//if Submit is pressed go to SecondViewController
if ([segue.identifier isEqualToString:#"secondSegue"]) {
SecondViewController *vc = [segue destinationViewController];
vc.array = _words;
}
}
So here is the code of the SecondViewController that receives the array
- (void)viewDidLoad
{
[self Calculate];
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(void)Calculate
{
int size = [array count];
NSLog(#"size is %d",size);
}
the log always says "size is 0".
Did i make some mistake?
Could it be that the Segue happens before i can read the TextView and fill the array so it's always empty?
thanks in advance!
EDIT:
I tried to NSLog the Submit action and i discovered that the program never accesses to it, so it never reads! (p.s. yes i connected the button)
So the segue happens before the Submit action
How can i solve? can i copy the PrepareForSegue code in the Submit action?
Try copying the array when you send it, Could be getting freed also are you sure -(IBAction)submit.. is being called?
Also the submit button I assume has the action that performs the segue . I'm not sure on the order of execution. Try calling the method Submit from prepare for segue and not from the button. That way you can guarantee it is being called first.
Remove the #synthesize, the compiler will automatically synthesize
the ivar to be _words
Use self.words when assigning and accessing the 'words' array. This ensures the ivar gets set properly as well, if you plan to refer to it directly (which you won't in most cases).
Your problem is that #synthesize creates an ivar called 'words', not '_words'.

Set delegate of viewcontrollers in UITabbarController

I have the following setup in my app:
A UITabbarController with 3 viewcontrollers, with embeded UINavigationControllers.
The 3 viewcontrollers inheret/superclass from a UIViewController subclass called "SVC", in order to implement elements which is used in all of the 3. viewcontrollers and prevent duplicated code. In this "SVC" class I have setup a delegate called "dismissDelegate" (which is used to tell when the tabbarcontroller is dimissed).
#protocol ModalViewDelegate <NSObject>
- (void)didDismissModalViewFrom:(UIViewController *)viewController;
#end
#property (weak, nonatomic) id <ModalViewDelegate> dismissDelegate;
My other viewcontroller which segues to the UITabbarController, implements this delegate in order to get information about, when the tabbarcontroller is dismissed.
the SVC class notifies the delegate of dismissal of the tabbar like so:
[self.dismissDelegate didDismissModalViewFrom:self];
I now want to set the delegate of all the viewcontrollers which inherts from the SVC class (all the tabbar viewcontrollers) to this viewcontroller and I try to do this via the prepareToSegue method like so:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([[segue identifier] isEqualToString:#"ToTab"]) {
UITabBarController *tabBarController = segue.destinationViewController;
for (UINavigationController *navController in tabBarController.viewControllers) {
for (UIViewController *vc in navController.viewControllers) {
_SubclassVC = (SVC *) vc.superclass;
_SubclassVC.dismissDelegate = self;
}
}
}
}
But I get the following error:
+[SVC setDismissDelegate:]: unrecognized selector sent to class 0xbca68
My questions:
Is this the right way to tackle this senario (get information about dismissal of a viewcontroller and setup this delegate in a subclass which is inhereted by multiple viewcontrollers)?
How do I manage to set my first viewcontroller as the delegate of all the viewcontrollers in the tabbar - the SVC class, so I can get notified when the tabbarcontroller is dimissed and solve the error?
+[SVC setDismissDelegate:]: unrecognized selector sent to class 0xbca68
See the +
The plus sign idicates that you are calling a class method. You must have tried setting a class variable by a setter. But a property represents instance variables only. Therefore the setters and getters that are automatically generated are intance methods only. (starting with a minus - in error messages like this).
And that is what you do:
_SubclassVC = (SVC *) vc.superclass;
_SubclassVC.dismissDelegate = self;
For whatever reason (probably by mistake or misunderstanding) you take the vc instance and get its superclass. vc.superclass returns a class object, not an object (meaning not an instance, in Obj-C class objects are objects too).
Then you typecast it to (SVC *) just to stop the compiler from throwing errors (or warnings - not sure).
Well, I guess that you wondered yourself why you have to typecast it at all. That's the reason :)
Next, you assign self to a property dismissDelegate. The compiler does that because you typecasted it to SVC* which does have a property dismissDelegate. The compiler will actually call the setter setDismissDelegate as usual in contructs like this.
BUT at runtime the message (or selector) setDismissDelegate: is not sent to an SVC* but to a class object. And the class SVC does not have a method (or selector) +setDismissDelegate: and therefore cannot resolve the message. And that is exactly what the error message is telling you.
All right, now we get to your questions.
1. Well, it is not the way I would do it, but that is certainly one way of achiving it.
2. If you want to stick with that unusual approach then do this minor change and you will get rid of the error:
for (SVC *vc in navController.viewControllers) {
vc.dismissDelegate = self;
}
There is no point in fetching the superclass object. If you cannot access the property of a superclass then you did something wrong with the inheritance chain.
If you want to be on the save side:
for (UIViewController *vc in navController.viewControllers) {
if (vc isKindOfClass:[SVC class]){ //BTW, this is good usage of the class object
SVC *svc = (SVC*) vc;
svc.dismissDelegate = self;
}
}

Delegate is nil

I have a property on a ViewController which I set from a parent SplitViewController:
Property declaration/synthesization
#interface DownloadRecipesViewController_iPad : DownloadRecipesViewController<PopoverMenuDelegate, RecipeChangeDelegate>{
id <NSObject, PopoverMenuParentDelegate, RecipeChangeDelegate, RecipeDownloadedDelegate> _selectionDelegate;
}
#property (strong) UIBarButtonItem *btnMenu;
#property (strong) id <NSObject, RecipeChangeDelegate, RecipeDownloadedDelegate> selectionDelegate;
#implementation DownloadRecipesViewController_iPad
#synthesize btnMenu;
#synthesize selectionDelegate = _selectionDelegate;
I wire up the delegate in the parent SplitViewVC's viewDidLoad method:
Wiring up the delegate
self.downloadVc = [self.storyboard instantiateViewControllerWithIdentifier:#"RecipeDownload"];
[self.downloadVc setSelectionDelegate:self];
A button in the Child VC calls a method to fire an event up to the parent ViewController, but when this event is called, the delegate is nil and the event isn't fired. I've wracked my brains trying every which way to find out why this happens but I'm at a total loss.
Delegate is nil here (firing the delegate):
-(IBAction)didTapMenu:(id)sender{
if([_selectionDelegate respondsToSelector:#selector(shouldToggleMenu)])
[_selectionDelegate shouldToggleMenu];
}
I've also tried without the backing property but hit the same problem.
Suggestions on how to find this follow. But first, why not save yourself some typing and remove the ivar and remove the #synthesize - its totally unnecessary typing at this time. Also, as a comment said, delegates should almost always be typed as weak.
Suggestions:
1) Write a (temporary) setter for the selectionDelegate, then set a break point where you actually set the value (or after) so you can verify that its getting set, and that nothing else is zeroing it out.
2) Set a breakpoint on the IBAction method, on the line where the if statement is, and when you hit it verify the object is the same one where you set the delegate, what the delegate value is, and then see if the respondsTo method succeeds (use single step).
The way I eventually solved this was:
Create a new delegate which exposes a method: -(void)segueBeginning:(UIStoryboardSegue*)destination
Use this delegate to expose prepareForSegue from the child UINavigationController to the parent SplitViewController
.3. Hook up my child VC in the parent ViewController when prepareForSegue is raised in the child nav controller:
if([destination.destinationViewController isKindOfClass:[DownloadRecipesViewController_iPad class]] && !self.downloadVc){ // download recipes
self.downloadVc = destination.destinationViewController;
self.downloadVc.selectionDelegate = self;
[self.downloadVc setSnapshotChangeDelegate:self];
[self.downloadVc.navigationItem setRightBarButtonItems:[NSArray arrayWithObjects:btnAdd, nil]];
}

Best practice to send "reloading" signal to UITableView from one view to another

My goal is to notify a UITableView to refresh itself every time some configurations have changed. The problem is that the configuration view is "not" on the same view that produces the signal. (Yes, I used Tabbed Application.)
Currently I use a sort of global variable in AppDelegate for detecting the change in one view, and do the check in another view. This is fine but the code is not readable as it is so tightly coupling. Is there an elegant method for doing this? Do I miss something in this programming framework?
If there were such an elegant way, I suppose the refreshing process of UITableView should happen as soon as the notification occurs. In this case, I would like to know whether it's possible to delay UITableView from refreshing itself until viewDidAppear occurs.
I would use KVO (Key Value Observing) to keep track of when it changes:
- (void)viewDidLoad {
[super viewDidLoad];
// Note that you can use the options to get the new value passed when it
// changes if you want to update immediately.
[configurationObject addObserver:self forKeyPath:#"configurationItem" options:0 context:nil];
}
- (void)viewDidUnload {
[super viewDidUnload];
[configurationObject removeObserver:self forKeyPath:#"configurationItem"];
}
// Note that I would refresh in viewWillAppear instead of viewDidAppear
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.needToRefreshData == YES) {
[self.tableView refreshData];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if (keyPath isEqualToString:#"configurationItem") {
[self.needToRefreshData = YES];
}
}
Use Delegation Design Pattern to pass data from one View Controller to the Other.
For example, let's say one Tab shows a list of cars in a UITableViewController and you have another view that let's a user add a new car to the list. You can let the UITableViewController
Adopt AddCarViewController's protocol
Set itself as a Delegate for AddCarViewController's protocol
Implement its protocol method
Execute the protocol method when informed
You can then let the AddCarViewController
Create a Protocol
Declare object reference Delegate with getter and setter methods
Define a method under that protocol
Inform the Delegate when the Save action is performed
Take a look at the following sample code for your UITableViewController
#interface ViewController : UITableViewController <AddCarViewControllerDelegate>
:
:
// The addCar: method is invoked when the user taps the Add button created at run time.
- (void)addCar:(id)sender
{
// Perform the segue named ShowAddCar
[self performSegueWithIdentifier:#"ShowAddCar" sender:self];
}
:
:
// This method is called by the system whenever you invoke the method performSegueWithIdentifier:sender:
// You never call this method. It is invoked by the system.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString *segueIdentifier = [segue identifier];
if ([segueIdentifier isEqualToString:#"ShowAddCar"]) {
// Obtain the object reference of the destination view controller
AddCarViewController *addCarViewController = [segue destinationViewController];
// Under the Delegation Design Pattern, set the addCarViewController's delegate to be self
addCarViewController.delegate = self;
// Instantiate a Save button to invoke the save: method when tapped
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:addCarViewController action:#selector(save:)];
// Set up the Save custom button on the right of the navigation bar
addCarViewController.navigationItem.rightBarButtonItem = saveButton;
}
}
:
:
- (void)addCarViewController:(AddCarViewController *)controller didFinishWithSave: (BOOL)save {
:
:
}
Sample code for the AddCarViewController is here
#protocol AddCarViewControllerDelegate;
#interface AddCarViewController : UIViewController
#property (nonatomic, strong) IBOutlet UITextField *carMake;
#property (nonatomic, strong) IBOutlet UITextField *CarName;
#property (nonatomic, assign) id <AddCarViewControllerDelegate> delegate;
// The keyboardDone: method is invoked when the user taps Done on the keyboard
- (IBAction)keyboardDone:(id)sender;
// The save: method is invoked when the user taps the Save button created at run time.
- (void)save:(id)sender;
#end
/*
The Protocol must be specified after the Interface specification is ended.
Guidelines:
- Create a protocol name as ClassNameDelegate as we did above.
- Create a protocol method name starting with the name of the class defining the protocol.
- Make the first method parameter to be the object reference of the caller as we did below.
*/
#protocol AddCarViewControllerDelegate
- (void)addCarViewController:(AddCarViewController *)controller didFinishWithSave:(BOOL)save;
#end
Well, one approach would be to have some common class (singleton perhaps which app delegate kind of is) that keeps track of your model, when the settings viewController detects a change it can mark the model as changed, then when the view in question comes in to view, ie, viewDidAppear gets called, it can query the model to see if the changed flag has been set, if it has then you know to reload the table view, otherwise you dont...
Another way could be to use notification center for it, if your view is loaded it can sign up for the notifications of the model change, in which at point it sets a flag that it needs to reload the table view next time it comes on screen..
hope this helps
You could store the configuration in core data and use an NSFetchedResultsController with the dependant view controller set as a delegate. This way your view controller will get a callback whenever the data is changed.
Apple has some boilerplate code to handle the updates as well

Resources