I declared a protocol in the header file of a Controller that manages a map view.
#protocol UCMapViewDelegate <NSObject>
#required
- (void)pushMapviewRight;
#end
I'm declaring the implementation of the protocol in another view controller (.h) and implement it in the .m file
// in the UCRootViewController.h
#interface UCRootViewController : UIViewController <UCMapviewDelegate>
// in the UCRootViewController.m
- (void)pushMapviewRight
{
NSLog(#"push mapview right");
}
I'm setting the delegate to a property that points to the rootviewController. This is done in the viewDidLoad() of my MapviewController, with a property #property (weak, nonatomic) id<UCMapViewDelegate> delegate;.
// in UCRootViewController
self.mapviewController.rootviewController = self;
// in UCMapViewController
self.delegate = (id<UCMapviewDelegate>)self.rootviewController;
Calling the delegated method. showMenu() gets executed when a button in the mapviewController gets pressed and it works. but the delegate method does NOT get called.
- (void)showMenu
{
NSLog(#"show menu");
[self.delegate pushMapviewRight];
}
But nothing happens.. what is wrong?! Help is greatly appreciated!
I fixed it. At first I used NSLog to verify that self was not nil (which is pretty obvious
, but still) I'm actually not sure why, but self.mapviewController.rootviewController = self; did not "carry over" to the point where I wanted to reference self.rootViewController, although self was not nil at the point where I set it to be the pointer to rootViewController.
I fixed it by creating another initWithRootViewController:(UCRootViewController*) ctrland passed self as an argument when I created the MapViewController.
Can someone explain why the valid reference to self (=rootViewController), was not available in the MapViewController?
Is the rootViewController property a strong reference or a weak one? It should probably be strong. If there are no weak references to the object, then it will be immediately released, and weak references get nilled out when the objects they point to are released. You need a strong reference somewhere in your application for objects to hang around.
As #CodaFi said, your codes look messy. Why do you set delegate property value in UCMapViewController? The delegate should be set in its parent when popup or prepare for the segue.
Basically, if your UCMapViewController has knowledge about UCRootViewController implementing a delegate method, why not call its method from riitVuewController directly? No need to set delegate at all.
Here is one example of using storyboard and segue, UCMyViewController is going to push segue to UCMapViewController:
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
...
[segue.destinationViewController setDelegate:self.rootViewController];
...
}
Related
There's a way to remove self as observer from all notifications:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Is there a similar way to remove self as a delegate of all objects that the viewController is a delegate of?
I want to place it in dealloc method in my prototype viewController. The reason is that sometimes when I dismiss a viewController, I get a crash with a message that a UIScrollView (or any of the official subclasses) called a method on its delegate (the viewController) that has since been deallocated. Since I consciously intend to call no such methods, I'd rather set self.delegate = nil. But since this is a prototype viewController, I don't have outlets to those scrollViews, hence my question.
No, there is no such way. Once we are done with the delegate, it requires to simply nil that reference.
Also before calling any delegate method make sure, to check nil condition as well method availability condition like:
if (_delegate != nil && [self.delegate respondsToSelector:#selector(myDelegateMethod:)]) {
// then only call that delegate method
[self.delegate myDelegateMethod:param];
}
Once you are done with all your delegate things nil your reference:
self.delegate = nil;
I don't know any built-in mechanism for it. I think, that the code that was responsible for making your object a delegate should be responsible for freeing it of this responsibility. You could provide some interface for it, but it depends on your code.
Also, since delegates are weak-referenced, they will be automatically set to nil when the delegate object is deleted, but it's not your case, I believe.
UPDATE:
Since in your case delegates don't seem to be declared as weak, I guess, the only option is to keep track of all objects that set your viewController as their delegate manually.
For instance:
In your ViewController:
#interface YourViewController
#property (nonatomic, strong) NSMutableDictionary *objectsThatDelegateSomethingToUs;
#end
#implementation YourViewController
-(void)makeDelegateOfObject:(id)obj withDelegatePropertyName:(NSString*)delegatePropertyName {
[self.objectsThatDelegateSomethingToUs setObject:delegatePropertyName forKey:obj];
}
-(void)dealloc {
for (id obj in self.objectsThatDelegateSomethingToUs.allKeys) {
[obj setValue:nil forKey:[self.objectsThatDelegateSomethingToUs valueForKey:obj]];
}
}
#end
Where you set your viewController as a delegate:
scrollView.delegate = viewController;
[viewController makeDelegateOfObject:scrollView withDelegatePropertyName:#"delegate"];
But, sadly, in this case you'll have to set your ViewController as a delegate programmatically. If delegate property is always called delegate, NSMutableArray should do the trick.
Actually, it's weird that scrollView keeps working when it's parent ViewController is deallocated. May be this is the real problem, and it can be fixed somehow, but, unfortunately, I can't give you any advice on it now, so my answer is trying to deal with the problem that you originally asked about. I recommend you to leave the question open for a while, may be someone else will be able to suggest a better solution.
P.S. Check out Logan's comment to your question. If all objects that use your ViewController as their delegate, are parts of ViewControllers' view hierarchy, then his solution is simpler and more elegant.
Placing this code in my superclass' dealloc method solved my crashes:
for (id view in self.view.subviews) {
if ([view isKindOfClass:[UIScrollView class]]) {
[view setDelegate:nil];
}
}
I was considering doing this in my viewController where I have direct access to the UIScrollView. However, because ARC disallows calling [super dealloc], this does not allow the dealloc code in my superclass to be called.
I saw this answer of how to create a placeholder for UITextView.
I took the following steps:
Add to the .h class the declaration:
#interface AdjustPhotoViewController : UIViewController<UITextViewDelegate>
Added the method:
- (BOOL) textViewShouldBeginEditing:(UITextView *)textView
{
NSLog(#"%d",[textView tag]);
if ([textView tag]==1){
campaignTitle.text = #"";
}else{
campaignDescription.text = #"";
}
return YES;
}
But I don't see that the method is being invoked!
What am I missing?
textView is already delegated via the storyboard to the view
SOLVED:
The problem was that it wasn't delegated. Although I was using storyboard - it was only an outlet, not a delegate.
Remember that if you are using storyboard, you need to delegate also from the text view to the orange button of the view! not only the other way
What am I missing?
Actually setting the delegate.
textView.delegate = self;
Merely conforming to a protocol won't magically make your object into the delegate of an arbitrary object; that's just a formal thing, and anyways, how on Earth would the UITextField know which particular instance of the class it has to assign its delegate?
VC1 = NewGameViewController
VC2 = GameViewController
NewGameViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if( [segue.identifier isEqualToString:#"newGameSegue"]) {
GameViewController *gameVC = (GameViewController *)segue.destinationViewController;
NSArray *array = [self nameArrayForTextFieldArray:self.namePicker.textFieldArray withColon:YES];
gameVC.nameArray = [[NSArray alloc] initWithArray:array];
}
-(NSArray *)nameArrayForTextFieldArray:(NSArray *)array withColon:(BOOL *)bool
basically returns an nsarray of strings given an nsarray of textfields. withcolon is a bool of whether or not you want the strings to have a colon appended to the end.
when i debug my code, the _nameArray ivar in gameVC still reads nil after every line here is called...can anyone help me out here??
The prepareForSegue method is invoked by UIKit when a segue from one screen to another is about to be performed. It allows us to give data to the new view controller before it will be displayed. Usually you’ll do that by setting its properties.
The new view controller can be found in segue.destinationViewController. If GameViewController embed the navigation controller, the new view controller will not be GameViewController but the navigation controller that embeds it.
To get the GameViewController object, we can look at the navigation controller’s topViewController property. This property refers to the screen that is currently active inside the navigation controller.
To send an object to the new view controller you can use this solution using performSegueWithIdentifier:
For example, if we want to perform a segue pressing a UIButton we can do this:
In the MyViewController.h we create a IBAction (connected to UIButton), dragging the button from storyboard to code:
- (IBAction)sendData:(id)sender;
In MyViewController.m we implement the method:
- (IBAction)sendData:(id)sender
{
NSArray *array = [self nameArrayForTextFieldArray:self.namePicker.textFieldArray withColon:YES];
[self performSegueWithIdentifier:#"newGameSegue" sender:array];
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"newGameSegue"]) {
UINavigationController *navigationController = segue.destinationViewController;
GameViewController *controller = (GameViewController *)navigationController.topViewController;
controller.nameArray = sender;
}
}
Is GameViewController embedded in a navigation controller? In that case, your destinationViewController property is of type UINavigationController, not GameViewController. You can get to GameViewController by calling [segue.destinationViewController.viewControllers lastObject].
I'm assuming that you've done a NSLog (or examine it in the debugger) of array immediately before setting gameVC.nameArray. You really want to make sure it's being set the way you think it is. It's amazing how many times I've spent debugging something like this only to realize the problem was in my equivalent to nameArrayForTextFieldArray. Or a typo in the name of the segue identifier. Or random things like that.
Assuming that's ok, then a couple of things are possible:
How is your nameArray property defined in GameViewController? If it's not a strong reference (or a copy), then when your array falls out of scope, it will be deallocated. I think this would manifest itself slightly differently, but it's worth confirming.
Also, I've seen situations where a controller like GameViewController might have some confusion between various ivars and properties (which is why I never define ivars for my properties ... I let #synthesize do that).
I assume you're not using a custom setter for nameArray. I just want to make sure. If so, though, please share that, too.
Bottom line, can you show us all references to nameArray in your #interface of GameViewController as well as in its #synthesize statement?
EDIT: changed the title. I didn't know it at the time but this is a duplicate of Why am I crashing after MKMapView is freed if I'm no longer using it?
This question is similar to Why is object not dealloc'ed when using ARC + NSZombieEnabled but different enough that I thought it worth throwing out there in case anyone understands and can explain to me what is happening. The other question may be an XCode bug so I presume this could be similar.
Scenario:
RootViewController has a tableView displaying a bunch of items
Selecting a cell presents a modal detailViewController containing another tableView
One of the table cells in detailViewController contains an MKMapView showing the location of the item
mapView.delegate = detailViewController
Dismiss the modal detailViewController
Soon after this, the app crashes b/c the MKMapView sends mapView:viewForAnnotation: to the now dealloc'ed detailViewController. This crash repro'ed on a users device with an ad-hoc distribution build so the issue has nothing to do with NSZombieEnabled.
I was able to resolve the crash by adding:
_mapView.delegate = nil;
to the dealloc method of the tableViewCell containing the mapView.
QUESTION: why is it necessary to nil the delegate when the cell is dealloc'ed? It seems like the mapView should be dealloc'ed by ARC when the cell is dealloc'ed leaving this unnecessary. It is good practice to nil delegates but I didn't think it would be required in this case.
EDIT: all subviews of both detailViewController and the UITableViewCells are declared as (nonatomic, strong) properties ala:
#property (nonatomic, strong) MKMapView * mapView;
EDIT 2: Guess I need to get better at reading the docs. #fluchtpunkt is correct. Here's the relevant info from the MKMapView documentation:
Before releasing an MKMapView object for which you have set a
delegate, remember to set that object’s delegate property to nil. One
place you can do this is in the dealloc method where you dispose of
the map view.
MKMapView is not compiled with ARC and because of that the property for delegate is still declared as assign instead of weak.
From the MKMapView documentation:
#property(nonatomic, assign) id<MKMapViewDelegate> delegate
And from the Transitioning to ARC Release Notes:
You may implement a dealloc method if you need to manage resources other than releasing instance variables. You do not have to (indeed you cannot) release instance variables, but you may need to invoke [systemClassInstance setDelegate:nil] on system classes and other code that isn’t compiled using ARC.
For delegates of system classes (NS*, UI*) you have to use the "old" rule of setting delegates to nil when you deallocate the delegate object.
so add a dealloc method to your detailViewController
- (void)dealloc {
self.mapView.delegate = nil;
}
While it's true that the delegates for such classes should be explicitly set to nil, doing it in dealloc is already too late. You already lost your reference to the mapview during viewDidUnload. You should do the self.mapView.delegate = nil BEFORE viewDidUnload (so probably viewWillDisappear or viewDidDisappear)
From my experience, only MKMapView and UIWebView behave this way.
I have declared a delegate for my cocoa application here :
MyAppDelegate.h
#interface MyAppDelegate : NSApplication {
}
- (void) applicationDidFinishLaunching:(NSNotification*) notice ;
#end
MyAppDelegate.m
#implementation MyAppDelegate
- (void) applicationDidFinishLaunching:(NSNotification*) notice {
NSLog(#"inside appdidfinishlaunching") ;
}
#end
I have linked the delegate outlet of File Owner to this object in IB.
Yet, this method is not getting called. I don't see any log messages from it.
Can you please suggest what is wrong ?
Your application delegate is not an application itself. It should inherit from NSObject, not NSApplication.
Why that matters
NSApplication is a singleton. Its init method always returns the first instance of NSApplication or any subclass, throwing away any subsequent objects you (or the nib loader) may be calling init on.
So you ended up setting your application object as its own delegate. The object you intended to make the delegate died in the second call to init, and the application object took its place.
Changing the application object to be an instance of your subclass would also have worked, but you'd still have the application as its own delegate, which is unclean and possibly dangerous (NSApplication may privately implement some of its delegate methods itself, as they're just notification handler methods). The only correct solution is to make your app delegate class not inherit from NSApplication.