ipad detect when UIPopoverControllers are dismissed - cocoa-touch

I have several uiPopoverControllers in my universal iPad app. I now have a requirement to trigger a function once a certain popover has been dismissed. I can do this easily if the user clicks "close" inside the popover, but if they touch the screen to hide the popover, I cannot trigger my function.
I've been googling for some time and cannot seem to find any delegate methods which I might be able to use in my main view controller to capture them. I would love something like didDismissPopoverController - but my guess is it's not available.
IF not, I guess the only thing to do would be to detect the touches and trigger then? Basically I am highlighting a UITableView row and loading the popover. I need to deselect the row - so want to simply call [table reloaddata].
Thanks for any help on this one!

You need to assign a delegate to the UIPopoverController and then implement the - (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController method. For example:
#interface FooController : UIViewController <UIPopoverControllerDelegate> {
// ...
}
// ...
#end
When you instantiate the UIPopoverController (say, for this example, in FooController)...
UIPopoverController *popover = // ...
popover.delegate = self;
Then, you would implement the method:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
// do something now that it's been dismissed
}
Granted, I haven't tested this but it seems like it should work...
Hope this helps!

You can use the popoverControllerDidDismissPopover delegate method after the following assignment:
self.popoverController.delegate = self;
Note that popoverControllerDidDismissPopover delegate method does not get called if you programmatically call [self.popoverController dismissPopoverAnimated:YES].

Related

tvOS preferredfocusedview is not always called

After a viewcontroller has been presented modally, the initial preferredfocusedview is called. However, after we dismiss the viewcontroller and it has been dealloc. preferredfocusedview is not called after presenting the viewcontroller again. Running on tvOS 9.2.
Even adding the following did not help:
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
}
Anyone know what's going on? Or if there's anyways to debug this?
Edit:
the way I am adding the viewcontroller:
viewController = [[UIViewController alloc] init];
[viewController addChildViewController:self];
[viewController.view addSubview:self.view];
[self didMoveToParentViewController:viewController];
If you are using a container view, having multiple ViewControllers or adding only one View Controller, the preferredFocusEnvironments method must be called from the rootView Controller indicating which View Controller to focus.
For eg.
View Controller A has a container View having ViewControllers B and ViewController C inside the Container.
View Controller A should have preferredFocusEnvironments returning which ViewController to focus.
This way, preferredFocusEnvironments on ViewController B or ViewController C will be called whenever the view becomes visible.
If the ViewController A doesn't have preferredFocusEnvironments, then it won't be called on the containerView ViewControllers.
Implementing custom focus behavior in tvOS 9 is disaster. Apple already mentioned that there is a limitation on redirecting focus specially when presenting/ dismissing a viewcontroller in WWDC.
tvOS10 will handle munch better with preferredFocusEnvironments.
https://developer.apple.com/videos/play/wwdc2016/215/
When I needed to fix this focus redirection issues in viewDidAppear in tvOS 9, I had exactly same issues. Sometimes it works, sometimes not. No clue what so ever. But after I put split second delay on setNeedsFocusUpdate / updateFocusIfNeeded in viewDidAppear it was way better in terms of consistency. preferredFocusedView get called all the time.
-(void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self setNeedsFocusUpdate];
[self updateFocusIfNeeded];
});
}
Do this in both presented and presenting view controllers, if you are manually changing focus. This is all from my observation and I don't think there is a proper way to achieve some focus behavior because tvOS API is kind of new and premature. Sorry about not being able to give you good explanation why this might work. Good luck.

How to perform IBAction before dismiss sheetViewController from same UIButton?

I am writing a Cocoa Mac app in Objective-C and using Storyboard for my UI.
I have a "Confirm" button in my sheetViewController.m which I want to perform some action (save some settings) as well as dismiss the sheetViewController at the same time. They both use the sheetViewController.m as outlets.
Unfortunately, with Storyboard, I can only pick one received action (IBAction) or dismissController.
I want to perform the IBAction FIRST, before dismissing the sheet. How can I accomplish this?
Happy to do this in code as well instead of Storyboard if necessary!
Thanks!
You can use codes to dismiss controller in your IBAction.
I found out how to reference Storyboard ID in code:
On MainViewController:
- (IBAction)didClickButton:(NSButton *)sender {
SheetViewController *sheetViewController = [self.storyboard instantiateControllerWithIdentifier:#"SheetViewController"];
// Must match Storyboard ID
[self presentViewControllerAsSheet:sheetViewController];
}
On SheetViewController:
- (IBAction)didClickButton:(NSButton *)button {
// Do something
[self dismissViewController:self];
}

dismissViewControllerAnimated completion block presenter view and modal view flow

I did find an answer to this title and I did do a little research but I'm still not getting the flow. Here is what I want to happen:
1) click a button on the presenter view to open a modal view. 2) retrieve some value and click a button to close the modal view....sending the value to the presentor view and execute a method.
I get that this works like a callback but I still can't figure out where to put the callback stuff.
So, how exactly do I do this? A) In the presentViewController completion block, should I include the presenter view method to execute when modal view is completed?
Or: B) In the modal view's dismissViewControllerAnimated completion block, should I include the presenter view method to execute when modal view is completed?
Can somebody help me with some sample code? Or at least help me get the flow of which block to put the code in?
Thank you, P
You talk about completion blocks so I am assuming you do not want to use delegates.
In the viewController that will be presented modally you need to provide a public completion handler, that will be called when it is dismissed.
#interface PresentedViewController : UIViewController
#property (nonatomic, strong) void (^onCompletion)(id result);
#end
Then in the implementation you need to call this completion block on dismissal. Here I assume the viewController is dismissed on a button click
- (IBAction)done:(id)sender
{
if (self.onCompletion) {
self.onCompletion(self.someRetrievedValue);
}
}
Now back in the viewController that presented the modal you need to provide the actual completion block - normally when you create the viewController
- (IBAction)showModal;
{
PresentedViewController *controller = [[PresentedViewController alloc] init];
controller.onCompletion = ^(id result) {
[self doSomethingWithTheResult:result]
[self dismissViewControllerAnimated:YES completion:nil];
}
[self presentViewController:controller animated:YES completion:nil];
}
This will create the new viewController to be presented modally and define what needs to happen on completion.
You can do this with delegates, that's the way Apple seems to recommend, but that seems like overkill to me. You have a reference to the presenter with the presentingViewController property, so you can just set the value of a property in the presenter from the presented controller in the button click method:
self.presentingViewController.someProp = self.theValueToPass;
[self dismissViewControllerAnimated:YES];
Using delegates is a good way to handle this:
In your PresentedViewController.h
#protocol PresentedViewControllerDelegate <NSObject>
-(void) viewWillDismiss;
#end
#property (nonatomic, weak) id <PresentedViewController> delegate;
Then in your PresentingViewController.h, you would subscribe to this delegate
#interface PresentingViewController : UIViewController <PresentedViewControllerDelegate>
in the .m you must implement the delegate method
- (void) viewWillDismiss {
}
and before you present the view controller set the delegate property you made as self.
presentingViewController.delegate = self;
Obviously not every implementation detail has been done here, but this should get you started.

Segue from callout in MKMapView

Hi I have a mapView that has annotations pop up, I want to be able to segue when the annotation callout button is clicked. I have some problems though when I do it. I have a few questions
1) Do I have to embed the mapViewController in a navigation Controller? If yes, my annotations do not show up when I do, how come?
2) does prepareforsegue get called from performSegueWithIdentifier?
3) when u send self, in this case what would self be?
Thanks
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[self performSegueWithIdentifier:#"Present Photo" sender:self];
}
Realized the problem it occurs here, I used to get a map controller from the id detail but now I think its a navigation controller, how do I get reference to the map controller now?
-(void) updateSplitViewDetail{
// ERROR OCCURS HERE!!! No longer map controller since I embed in navigation controller
id detail = [self.splitViewController.viewControllers lastObject];
if ([detail isKindOfClass:[MapViewController class]]) {
MapViewController *mapVC = (MapViewController*) detail;
mapVC.delegate = self;
mapVC.annotations = [self mapAnnotations];
}
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self updateSplitViewDetail]; //Error may be here
}
1)
Yes. If you want to perform a push segue, the source view controller (your map view controller) should be embedded in in navigation controller.
I'm not sure why your annotations/callouts aren't appearing in that case -- I've seen plenty of projects that work correctly that way. Perhaps your reference to the map view when you add the annotations isn't what you think it is? (And you're adding annotations to nil instead?) You'll need to provide more details for us to help. (Edit your question or post a new question since it's sort of a separate issue.)
2)
Yes. prepareForSegue:sender: is called after you call performSegueWithIdentifier:sender:.
3)
The "sender" argument in these methods is entirely for your own use -- its sole reason for existence is to allow you to pass some context from the code that calls performSegueWithIdentifier:sender: to the implementation of prepareForSegue:sender:. (Or in the case of segues automatically performed when the user taps some control, to allow your prepareForSegue:sender: implementation to know which control was tapped.)
So, pass whatever you want: self is fine, and so is nil if you're not making use of it. Or if it's useful for your prepareForSegue:sender implementation to know which callout was tapped, you might consider passing the annotation view's annotation as "sender" (say, so it can set up the destination view controller with appropriate info).

UIViewController parentViewController access properties

I know this question has been asked several times and I did read existing posts on this topic but I still need help.
I have 2 UIViewControllers - parent and child. I display the child UIViewController using the presentModalViewController as below:
ChildController *child =
[[ChildController alloc] initWithNibName:#"ChildView" bundle:nil];
[self presentModalViewController:child animated:YES];
[child release];
The child view has a UIPickerView. When user selects an item from UIPickerView and clicks done, I have to dismiss the modal view and display the selected item on a UITextField in the parent view.
In child's button click delegate, I do the following:
ParentController *parent =
(ParentController *)[self.navigationController parentViewController];
[parent.myTextField setText:selectedText];
[self dismissModalViewControllerAnimated:YES];
Everything works without errors. But I don't know how to load the parent view so that it displays the updated UITextField.
I tried
[parent reloadInputViews];
doesn' work. Please help.
Delegation is the way to go. I know some people that may be looking for an easier solution but trust me I have tried others and nothing works better than delegation. So anyone having the same problem, go read up on delegation and follow it step by step.
In your subviewcontroller.h - declare a protocol and declare delegate mthods in it.
#protocol myDelegate
-(void)clickedButton:(subviewcontroller *)subController;
#end
In your subviewcontroller.h, within #interface:
id<myDelegate> delegate;
#property (nonatomic, assign) id<myDelegate> delegate;
NSString *data;
-(NSString *)getData;
In your subviewcontroller.m, synthesize myDelegate. Add the following code to where you want to notify your parentviewcontroller that the subview is done doing whatever it is supposed to do:
[delegate clickedButton:self];
and then handle getData to return whatever data you want to send to your parentviewcontroller
In your parentviewcontroller.h, import subviewcontroller.h and use it's delegate
#import "subviewcontroller.h"
#interface parentviewcontroller : VUIViewController <myDelegate>
{}
In your parentviewcontroller.m, implement the delegate method
- (void)clickedButton:(subviewcontroller *)subcontroller
{
NSString *myData = [subcontroller getData];
[self dimissModalViewControllerAnimated:YES];
[self reloadInputViews];
}
Don't forget memory management!
If a low-memory warning comes in during your modal view's display, the parent's view will be unloaded. Then parent.myTextField is no longer referring to the right text field until the view is reloaded. You can force a reload of the view just by calling parent.view;
However, a better idea might be to have the parent view have a String property that can be set by the child view. Then, when the parent view reappears, put that data into the text field, inside viewWillAppear: for example. You'd want to have the value set to some default value for when the parent view initially shows up too.
-(void) viewWillAppear:(BOOL) animated doesn't get called for me either, exactly when it's a modal view controller. No idea why. Not incorrectly overridden anywhere in this app, and the same problem occurs on the other 2 apps I'm working on. I really don't think it works.
I've used the delegate approach before, but I think that following approach is pretty good as well.
I work around this by adding a private category to UIViewController, like so:
.h file:
#interface UIViewController(Extras)
// returns true if this view was presented via presentModalViewController:animated:, false otherwise.
#property(readonly) BOOL isModal;
// Just like the regular dismissModalViewController, but actually calls viewWillAppear: on the parent, which hasn't been working for me, ever, for modal dialogs.
- (void)dismissModal: (BOOL) animated;
#end
and .m file:
#implementation UIView(Extras)
-(BOOL) isModal
{
return self == self.parentViewController.modalViewController;
}
- (void)dismissModal: (BOOL) animated
{
[self.parentViewController viewWillAppear: animated];
[self dismissModalViewControllerAnimated: animated];
}
#end
which I can now call like this when I want to dismiss the dialog box:
// If presented as a modal view, dismiss yourself.
if(self.isModal)
[self dismissModal: YES];
and now viewWillAppear is correctly called.
And yes, I'm donating a bonus 'isModal' property, so that the modal view can tell how it was being presented, and dismiss itself appropriately.