Is there a way to distinguish between which UIPopOver is dismissed? - ios

I have several popovers in my application and I am having difficulty in determining which popover was dismissed. Is there a "tag" feature equivalent for UIPopOvers?
I can NSLog the popoverController in the popoverContorllerDidDismissPopover method and see the memory reference of each one but that doesn't help.
#pragma mark - Popover controller delegates
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
NSLog(#"Popover dismised %#", popoverController);
}

An extract from here:
If I understand the question, then basically, no - and it's maddening.
On the one hand you're told that only one popover should be showing at
any one moment. On the other hand you don't automatically get a
reference to that popover. Thus it is up to you to store a reference,
manually, to the current popover controller at the time it shows its
popover, so that you can talk to it later in order to dismiss it.
Popover controller management can thus get really elaborate and
clumsy; you're doing all kinds of work that the system should just be
doing for you.
iOS is funny this way. I'm reminded of how there's no call in iOS 4
that tells you current first responder. Obviously the system knows
what the first responder is, so why won't it tell you? It's kind of
dumb. This is similar; the system clearly knows useful stuff it won't
share with you. m.

There are many ways how to distinguish between popovers. I will list few of them:
You are asking about tag. Note that every popover has a content view controller and this controller has a view that can be tagged. However, using magic integer tags to distinguish between views is arguable in general.
Store the type of the popover into a variable/property in your controller, e.g. as an enum. This is the simplest way.
Add the neccessary information to the popover, but be clever about it, e.g.
#interface MyPopoverController : UIPopoverController
#property (nonatomic, copy, readwrite) void (^dissmissHandler)(void);
#end
#implementation MyPopoverController
- (id)initWithContentViewController:(UIViewController*)contentView {
self = [super initWithContentViewController:contentView];
if (!self) {
return nil;
}
self.delegate = self;
return self;
}
- (void)popoverControllerDidDismissPopover:(UIPopoverController*)popover {
assert(popover == self);
if (self.dissmissHandler) {
self.dissmissHandler();
}
}
#end
MyPopoverController* popover = [MyPopoverController alloc] initWithContentViewController:...];
popover.dissmissHandler = ^{
...
};

As #Anoop stated, you can usually only have one popover showing at a time.
One possible solution is to check the contentViewController property on the pop over. If you are storing a reference of each view controller you could do something like:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
if ( popoverController.contentViewController == self.someUIViewController ) {
// do stuff
}
else if ( popoverController.contentViewController == someoTherViewController ) {
//
}
NSLog(#"Popover dismised %#", popoverController);
}
If storing a reference to each content view controller is not possible (or maybe just not a good idea), you could always check its type:
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController {
if ( [popoverController.contentViewController isKindOfClass:[MyAwesomeViewController class]] ) {
// do stuff
}
else if ( [popoverController.contentViewController isKindOfClass:[MyOtherViewController class]] ) {
//
}
NSLog(#"Popover dismised %#", popoverController);
}
Another possible solution, which is probably better from a design stand point of view, would be to pass in a delegate to the view controller contained in the pop over. More here. This way, the view controller displayed can send data back to your main view controller.

Related

Am I properly using delegation? If not, how should I be doing this?

So I have taken the plunge into Objective-C programming for iOS development. I have a little app that I am working on, nothing special, but something to help teach me the ropes. The problem I am having is as follows:
Currently, I have two classes. The first being
ViewController
and the second being one that I created myself called
UserDecision
The View controller shows what is on screen, and UserDecisions currently takes the information from buttons pressed on screen, and performs the proper logic on it while working with my model class. My issue is, that I have an update UI method in UserDecision which needs to update the button properties (text, visibility, etc.) in ViewController if certain events take place. Because of this, I can't user an instance of ViewController because I won't be able to access the buttons on screen. So for this I created a delegate system:
#protocol updateUIDelegate <NSObject>
-(void)hideAll;
-(void)makeBackVisible;
-(void)updateOutput:(NSString *)output;
-(void)updateChoices:(NSString *)choices;
-(void)updateTrueButton:(NSString *)trueString;
-(void)updateFalseButton:(NSString *)falseString;
-(void)removeChoiceFromArray;
#end
The above protocol is defined in UserDecision.h, and then I assigned my ViewController as my delegate:
#interface ViewController : UIViewController <updateUIDelegate>;
And then I flush out said methods in my ViewController.m:
#pragma - updateUIDelegates -
//Called when the last screen is displayed
-(void)hideAll{
[_trueButton setHidden:true];
[_falseButton setHidden:true];
[_choicesText setHidden:true];
[_backButton setHidden:true];
[_resetButton setHidden:false];
}
//Makes back button visible
-(void)makeBackVisible{
[_backButton setHidden:false];
}
//Updates the text on the false button
-(void)updateFalseButton:(NSString *)falseString{
[_falseButton setTitle:falseString forState:UIControlStateNormal];
}
//Updates the text on the true button
-(void)updateTrueButton:(NSString *)trueString{
[_trueButton setTitle:trueString forState:UIControlStateNormal];
}
//Updates the output text box
-(void)updateOutput:(NSString *)output{
[_outputText setText:output];
}
//Updates the choices textbox
-(void)updateChoices:(NSString *)choices{
if(!choicesArray){
choicesArray = [[NSMutableArray alloc] initWithCapacity:4];
}
//If this is the first button press, add string to array and display
if([_choicesText.text isEqualToString:#""]){
[choicesArray addObject:choices];
_choicesText.text = [NSString stringWithFormat:#"%#", choices];
}
//Otherwise, add the new string to the array, and print the array
//using a comma as a way to concatinate the string and get rid of
//the ugly look of printing out an array.
else{
[choicesArray addObject:choices];
[_choicesText setText:[NSString stringWithFormat:#"%#",[choicesArray componentsJoinedByString:#", "]]];
}
}
//Removes the last choice from the array
-(void)removeChoiceFromArray{
[choicesArray removeLastObject];
[_choicesText setText:[NSString stringWithFormat:#"%#", [choicesArray componentsJoinedByString:#","]]];
}
This allows me to call theses methods by sending them as a message to self.delegate in my UserDecision class when needed.
This is my current setup. My issue has become that I want to create a modal seque view that pops up at the end (after a user presses a button to bring up the view), and which can be dismissed afterward. The problem I have is that this view, from the reading and research I have done online, can only be dismissed through delegation, unless I want things to get nasty. Now, I tried to implement this information in my class, but then I read that a class can only be a delegate to one other class. And since my ViewController(which is my main window) is already a delegate of my UserDecision class, I can't make it a delegate of the new View I have created, and thus can't dismiss the view. So, I am here to ask for your help. How can I go about solving this issue?
Also, for more of my code, should you want to have a look, here is a link to my gitHub: https://github.com/Aghassi/Xcode/tree/master/Bubble%20Tea%20Choice/Bubble%20Tea%20Choice
I read that a class can only be a delegate to one other class. And
since my ViewController(which is my main window) is already a delegate
of my UserDecision class, I can't make it a delegate of the new View I
have created
I don't believe that's true. You can make ViewController implement many different protocols, therefore being delegates to different classes/objects.
For example: (UITableViewDelegate and UITextViewDelegate can both be implemented on the same ViewController for 2 separate objects (UITextView and UITableView).
As for using delegation to close modal windows, another option is to use blocks as well.
It is possible for a viewController to dismiss itself. Just hook up a dismiss button to a function that calls something like:
[self.presentingViewController dismissViewControllerAnimated:YES];
or
[self.navigationController popViewControllerAnimated:YES];
Dismissal can be done with a delegate pattern but it is not required for everything.
You viewController class can be a delegate of multiple objects so it should be able to dismiss the modal view. The only issue is if its a delegate of multiple objects of the same class you may need to check which object is calling it.
Look at the tableView delegate methods as an example, the tableView calls them passing itself as the first parameter.
To dismiss a custom modal view you would define a different protocol anyway so there would be no problem with calling the same method.
See example below:
#protocol OSImageViewControllerProtocol <NSObject>
- (void)dismissImageViewer;
#end
#implementation OSImageViewController
- (void)loadView
{ //LOG(#"loadView called");
scrollView = [[ImageScrollView alloc] init];
scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
UITapGestureRecognizer *doubleTapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(scrollViewDoubleTapped:)];
doubleTapRecognizer.numberOfTapsRequired = 2;
doubleTapRecognizer.numberOfTouchesRequired = 1;
[scrollView addGestureRecognizer:doubleTapRecognizer];
self.view = scrollView;
}
- (BOOL)prefersStatusBarHidden {
return NO;
}
- (void)scrollViewDoubleTapped:(UITapGestureRecognizer*)recognizer {
//LOG(#"scrollViewDoubleTapped called");
[self.delegate dismissImageViewer];
}
#end
#implementation ViewController
-(void)browseImage:(UIImage*)image
{
OSImageViewController *_imageViewerController = [[OSImageViewController alloc] init];
UIImage *img = [[UIImage alloc] initWithData:UIImagePNGRepresentation(image)];
_imageViewerController.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
_imageViewerController.modalPresentationStyle = UIModalPresentationFullScreen;
_imageViewerController.delegate = self;
[self presentViewController:_imageViewerController animated:YES completion:^(void){
[_imageViewerController setImage:img];
}];
}
- (void)dismissImageViewer {
[self dismissViewControllerAnimated:YES completion:nil];
}
#end
I believe you want to display a modal view from your ViewController.
Let the the modal view be managed by say ViewController2. In ViewController2.h declare a protocol of ViewController2
#protocol viewController2Delegate
-(void)dismissViewController2;
#end
Now make ViewController implement this protocol
#interface ViewController : UIViewController <updateUIDelegate,viewController2Delegate>
Add the method to the ViewController.m
-(void)dismissViewController2
{
[self dismissViewControllerAnimated:YES completion:nil];
}
Now whenever you push a modal view(managed by ViewController2) from ViewController you set the delegate to self. Your ViewController.m code might look like this
ViewController2 *objViewController2 = [[ViewController2 alloc]init];
objViewController2.delegate = self;
[self presentViewController:objViewController2 animated:YES completion:nil];
Hope this solves your problem
COMMUNICATION PATTERNS
Delegation is one of the communication patterns that more or less loosely coupled objects use to communicate each other. iOS Framework provides the following patterns: KVO, Notification, Delegation, Block, Target-Action.
In general, there are cases where the choice comes down to a matter of taste. However, there are many cases that are pretty clear cut.
It's also important to note that the use of each of this patterns results in a certain level of coupling among objects involved into the communication process.
Let's focus now on Delegation, Block, Target-Action.
DELEGATION
Level of coupling (proportional to the level of mutual ignorance) : loose
It allows us to customize an object’s behaviour (decoration) and to be notified about certain events (callback). In this case, the coupling is pretty loose, because the sender only knows that its delegate conforms to a certain protocol.
Since a delegate protocol can define arbitrary methods, you can model the communication exactly to your needs. You can hand over payloads in the form of method arguments, and the delegate can even respond in terms of the delegate method’s return value. Delegation is a very flexible and straightforward way to establish some sort of blind communication between two object that should be loosely coupled for design reason. Let's think to the communication mechanism between a tableview and its dataSource delegate.
Conversely, if two objects are that tightly coupled to each other that one cannot function without the other, there’s no need to define a delegate protocol (use composition instead). In these cases, the objects can know of the other’s type and talk to each other directly. Two modern examples of this are UICollectionViewLayout and NSURLSessionConfiguration.
TARGET-ACTION
Level of coupling : very loose
Target-Action is the typical pattern used to send messages in response to user-interface events. Both UIControl on iOS and NSControl/NSCell on the Mac have support for this pattern. Target-Action establishes a very loose coupling between the sender and the recipient of the message. The recipient of the message doesn’t know about the sender, and even the sender doesn’t have to know up front what the recipient will be. In case the target is nil, the action will goes up the responder chain until it finds an object that responds to it. On iOS, each control can even be associated with multiple target-action pairs.
A limitation of target-action-based communication is that the messages sent cannot carry any custom payloads. On the Mac action methods always receive the sender as first argument. On iOS they optionally receive the sender and the event that triggered the action as arguments. But beyond that, there is no way to have a control send other objects with the action message.
BLOCK
Blocks are usually used to pass to an object a behaviour to be executed before its lifecycle end. Besides, they can also substitute delegates with a caveat relevant to the potential creation of retain cycle.
self.tableView.didSelectRowAtIndexPath = ^(NSIndexPath *indexPath) {
...
[self.tableView reloadData];
...
}
In this case the sender retain the table view whose selection block retain the sender so we'd better use delegation pattern.
An example in which block communication shines:
self.operationQueue = [[NSOperationQueue alloc] init]
Operation *operation = [[Operation alloc] init];
operation.completionBlock = ^{
[self finishedOperation]
}
[operationQueue addOperation:operation];
There's a retain cycle in the above code as well, but once the queue removes the operation, the retain cycle is broken.
Blocks are a very good fit if a message we call has to send back a one-off response that is specific to this method call, because then we can break potential retain cycles. Additionally, if it helps readability to have the code processing the message together with the message call, it’s hard to argue against the use of blocks. Along these lines, a very common use case of blocks are completion handlers, error handlers, and the like.
A CHART HELPING US TO MAKE THE RIGHT CHOICE
source: objc.io
In your specific case, I'd use the target-action communication pattern to dismiss the presented modal view controller.
For example,
ModalViewController *modalViewController = [[ModalViewController alloc] init];
[self presentViewController:modalViewController animated:YES completion:^{
[modalViewController.closeButton addTarget:self action:#selector(dismissModalViewControllerAnimated:)
forControlEvents:UIControlEventTouchUpInside];
}];

Where I should I prepare my data? In awakeFromNib, viewDidLoad or something else

I am still relatively new to iOS programming. Here is a question that confused me for a long time.
So in one of the view controllers, before this view controller is pushed into the navigation item, I am passing one parameter, say userId, to it in the prepareForSegue from previous view controller. And when this view controller is loading (initialising) based on the userId from the previous view controller, I am making a network call to fetch a list of information that's related to this user and then populating this information to the model of the current view controller.
Where should I put the logic of this data preparation?
Using viewDidLoad: should be fine for common storyboard use because the storyboard does not reuse view controller. Anyway, for the completeness of my view controller usage scenario, I tend to use this pattern:
Start loading remote data asynchronously in viewWillAppear:
Stop loading remote data in viewWillDisappear:
This make sure that your data will be always updated to the current userId because the ID might be changed after viewDidLoad, e.g. in case of view controller reuse or accessing .view property before setting userId.
You should also track if your data has been loaded. For example, you could make a private boolean field named _isDataLoaded, set it to true when finish loading data and set it to false when cancelling loading data or setting new userId.
To sum it up, the pattern in my idea should be something like this:
#interface UserViewControler : UIViewController {
bool _isDataLoaded;
NSURLConnection _dataConnection;
}
#implementation UserViewController
-(void) setUserId:(int)userId {
if (_userId != userId) {
_userId = userId;
_isDataLoaded = false;
}
}
-(void) viewWillAppear:(BOOL)animated {
if (!_isDataLoaded) {
_dataConnection = // init data connection here
_dataConnection.delegate = self;
[_dataConnection start];
}
}
-(void) viewWillDisappear:(BOOL)animated {
if (_dataConnection) {
[_dataConnection cancel];
_dataConnection = nil;
_isDataLoaded = false;
}
}
// NSURLConnection call this when finish
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
_isDataLoaded = true;
_dataConnection = nil;
}
// NSURLConnection call this when fail to load data
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
_isDataLoaded = false;
_dataConnection = nil;
}
It depends on what framework you use to retrieve data from remote server, but the pattern should be like this. This will ensure that:
You will load data only when the view appear.
View controller will not loading more data after disappear.
In case of same userId, data would not be downloaded again.
Support view controller reuse.
- (void) viewDidLoad {
[super viewDidLoad];
// initialize stuff
}
Although, it may be better to do the network call and gather all this information into a custom class that contains all the information, and then perform the segue. Then all you have to do in the new view controller is pulled data out of the object (which would still be done in viewDidLoad).
Arguably, this method might be better because if there's a problem with the network, you can display an error message and then not perform the segue, giving the user an easier way to reattempt the same action, or at least they'll be on the page to reattempt the same action after leaving app to check network settings and coming back.
Of course, you could just segue forward always, and segue backward if there's a network error, but I think this looks sloppier.
Also, it's worth noting that if you're presenting the information with a UICollectionView or a UITableView, the presenting logic can (should) be moved out of viewDidLoad and into the collection/table data source methods.
What I have done in the past is make custom initializers.
+(instancetype)initWithUserID:(NSString)userID;
Here is an example of the implementation.
+(instancetype)initWithUserID:(NSString *)userID {
return [[self alloc] initWithUserID:userID];
}
-(id)initWithUserID:(NSString *)userID {
self = [self initWithNibName:#"TheNameOfTheNib" bundle:nil];
if(self) {
_userID = userID;
}
//do something with _userID here.
//example: start loading content from API
return self;
}
-(void)viewDidLoad {
//or do something with userID here instead.
}
The other thing I would suggest is make a custom class that loads data and uses blocks.
Then you can do something like this
[API loadDataForUserID:userID withCompletionBlock^(NSArray *blockArray) {
//in this case I changed initWithUserID to initWithUsers
[self.navigationController pushViewController:[NextController initWithUsers:blockArray] animated:YES];
}

Pass data to segue destination without iVar

Since switching to storyboards, I load a view controller via
[self performSegueWithIdentifier:#"identifier" sender:self]
This works perfectly. Now, if I want to set any properties on the destination view controllers, I implement the method prepareForSegue:sender: and set what properties I need to set. Everything works as expected without any problems.
Ever since I starting using this approach over the old
MyViewController *vc = ....
vc.prop = #"value";
[self.navigationController pushViewController:vc];
I've felt that passing parameters to the destination view controller is a little hacky, in particular if the value you're trying to set is not just a static value.
Lets say for example, I have a button which fetches some data from a server. When the data returns, it creates a new object, and then presents a new view controller to display this object. To do this, I call performSegueWithIdentifier:sender:, but that's the end of it. My object is now deallocated and no longer exists, and I have no way of passing it to the prepareForSegue:sender: method, unless I store it in an instance variable.
This feels pretty horrible, as the object isn't meant to last longer than this action, and has no relation to anything else in my current view controller.
In this situation, I understand that I could quite simply request the data in the new view controller but it's just an example.
My question is, is there another way of doing this without it feeling so hacky? Can I get this data into the destination view controller without storing it in an instance variable?
I know I could still use the old approach, but I'd like to stick with the storyboard methods if I can.
Well the sender parameter of the performSegueWithIdentifier:sender is the same one received by the prepareForSegue:sender. So if you want to send a variable to your prepareForSegue:sender the sender is your friend. In your case:
SomeViewController.m
-(void)aMethodThatDownloadsSomeDataFromServer {
NSString *exampleData = [self someDataThatIDownloaded];
[self performSegueWithIdentifier:#"yourSegueIdentifier" sender:exampleData];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if(segue.identifier isEqualToString:#"yourSegueIdentifier"]) {
if([sender isKindOfClass:[NSString class]]) { //maybe you want to send different objects
segue.destinationViewController.stringProperty = sender;
}
else {
segue.destinationViewController.objectPorperty = sender;
}
}
}
The accepted solutios is correct but I frequently use another approach when data are shared between more than two segue. I frequently create a singleton class (let's call it APPSession) and I use it as a datamodel, creating and maintaining a session-like structure I can write and read from everywhere in the code.
For complex applications this solution maybe requires too much error prone coding but I've used it succesfully in a lot of different occasions.
APPSession.m
//
// APPSession.m
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import "APPSession.h"
#implementation APPSession
#synthesize myProperty;
static APPSession *instance = nil;
// Get the shared instance and create it if necessary.
+ (APPSession *)instance {
if (instance == nil) {
instance = [[super allocWithZone:NULL] init];
}
return instance;
}
// Private init, it will be called once the first time the singleton is created
- (id)init
{
self = [super init];
if (self) {
// Standard init code goes here
}
return self;
}
// This will never be called since the singleton will survive until the app is finished. We keep it for coherence.
-(void)dealloc
{
}
// Avoid new allocations
+ (id)allocWithZone:(NSZone*)zone {
return [self sharedInstance];
}
// Avoid to create multiple copies of the singleton.
- (id)copyWithZone:(NSZone *)zone {
return self;
}
APPSession.h
//
// APPSession.h
//
// Created by Luca Adamo on 09/07/12.
// Copyright 2012 ELbuild. All rights reserved.
//
#import <Foundation/Foundation.h>
#interface APPSession : NSObject{
}
#property(nonatomic,retain) NSString* myProperty;
+ (id)sharedInstance;
#end
How to read and write the property myProperty from every part of the app code.
// How to write "MyValue" to myProperty NSString *
[APPSession instance] setMyProperty:#"myValue"]
// How to read myProperty
NSString * myVCNewProperty = [[APPSession instance] myProperty];
With this mechanism I can safely write for instance a value in the APPSession in the first ViewController, perform a segue to a second one, perform another segue to a third one and use the variable written during the first segue.
It's more or less like a SessionScoped JavaBean in Java EE. Please feel free to point out problems in this approach.
All of these answers are correct, but I've found a pretty cool way of doing this. I've tested only in iOS 7 and iOS 8
After declaring and setting the value of the object you wish to pass, in the prepareForSegue method,
[segue.destinationViewController setValue:event forKey:#"property"];
//write your property name instead of "property

Dismiss popover when iPad app goes in background

Hi I am working on a iPad app and got a requirement to dismiss all popovers (if any) when app goes in background.
I did some study online and didn't find a simple way to do it. I'd like to share some my idea here and see if there are a better way to do it.
1, Dismiss popovers in didEnterBakcground in delegate. Seems not practical since we have to add all popovers reference in.
2, Go through all views recursively in current window to find popover view by (class = _UIPopoverView). It is seems a bit hacky and dangerous.
3, Set up UIApplicationDidEnterBackgroundNotificationgroundNotification in each object who own popovers and dismiss them. This seems reasonable, but really troublesome if there are hundreds of popovers in your app.
4, How about add a category method say -(void)dismissWhenAppWillEnterBackground; and register notification.
Or there is easier way to do it?
Here is a drop-in category on UIPopoverController that does what you're asking.
Basically the category swizzles initWithContentViewController: so that it can track live UIPopoverController instances in a NSHashTable (which doesn't itself hold the contained UIPopoverControllers alive since it keeps weak references to them.) It also monitors for UIApplicationDidEnterBackgroundNotification, and when this arrives it iterates the collection of live UIPopoverControllers and dismisses any that are showing.
It might be nice to extend this to implement the "never allow two popovers to show at once" rule that Apple has.
I'm not a huge fan of method swizzling in production apps but this seems pretty safe.
No special instructions for use. Just include the category in your project and use your UIPopoverControllers normally.
#import <objc/runtime.h>
#interface UIPopoverController (autodismiss)
#end
#implementation UIPopoverController (autodismiss)
static NSHashTable* ts_popoverHashTable;
+ (void) load
{
SEL originalSelector = #selector(initWithContentViewController:);
SEL replacementSelector = #selector(ts_initWithContentViewController:);
Method originalMethod = class_getInstanceMethod( [UIPopoverController class], originalSelector);
Method replacementMethod = class_getInstanceMethod( [UIPopoverController class], replacementSelector);
method_exchangeImplementations(originalMethod, replacementMethod);
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector( applicationDidEnterBackgroundNotification: )
name: UIApplicationDidEnterBackgroundNotification
object: nil];
}
- (id) ts_initWithContentViewController: (UIViewController*) contentViewController
{
UIPopoverController* pc = [self ts_initWithContentViewController: contentViewController];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ts_popoverHashTable = [NSHashTable weakObjectsHashTable];
});
[ts_popoverHashTable addObject: pc];
return pc;
}
+ (void) applicationDidEnterBackgroundNotification: (NSNotification*) n
{
for ( UIPopoverController* pc in ts_popoverHashTable )
{
if ( pc.isPopoverVisible )
{
[pc dismissPopoverAnimated: NO];
}
}
}
#end
I may have a better answer, which is add a category method -(void)dismissWhenAppWillEnterBackground to UIPopoverController and register UIApplicationWillEnterBackgroundNotificationgroundNotification.
Write a protocol with a couple of optional methods:
- (void)appWillEnterBackground;
- (void)appWillBecomeActive;
Make your view controllers to implement it and then in your app delegate, access your root view controller, check if it responds to those methods and invoke them when the app is going to background and becoming active.
You should be able to obtain the root view controller easily. If you have a hierarchy of view controllers you may need to forward the call.
Add your popover dismissal code in appWillEnterBackground, for instance.
Create a uiviewcontroller base class for all the view controllers in the application.
Add an array which contains the references to the popover views in the particular viewcontroller
Maintain a reference to the current viewcontroller iin app delegate .
When app enters in background, get the current viewcontroller and travers the popover array and dismiss all the popovers.

UIActivity activityViewController not dismissing on iPad

I have a UIActivity subclass that creates its own activityViewController:
- (UIViewController *)activityViewController {
WSLInProgressViewController* progressView = [[[WSLInProgressViewController alloc] init] autorelease];
progressView.message = [NSString stringWithFormat:NSLocalizedString(#"Posting to %#...",#"Posting to..."),
self.activityType];
return progressView;
}
I've add a full repro on GitHub.
According to the documentation, you aren't supposed to dismiss this manually. Instead, the OS does that when you call activityDidFinish:. This works fine when ran on an iPhone.
When I say "works," this is the sequence of events that I'm expecting (and see on the iPhone):
Display the UIActivityViewController
User presses my custom activity
My view controller appears
I call activityDidFinish:
My custom view controller is dismissed
The UIActivityViewController is also dismissed
However, when I run this same code on the iPad Simulator -- the only difference being that I put the UIActivityViewController in a popup, as the documentation says you should -- the activityViewController never dismisses.
As I say, this is code wo/the popUP works on the iPhone and I have stepped through the code so I know that activityDidFinish: is getting called.
I found this Radar talking about the same problem in iOS6 beta 3, but it seems such fundamental functionality that I suspect a bug in my code rather than OS (also note that it works correctly with the Twitter and Facebook functionality!).
Am I missing something? Do I need to do something special in the activityViewController when it's run in a UIPopoverViewController? Is the "flow" supposed to be different on the iPad?
The automatic dismissal only appears to happen when your 'activity' controller is directly presented, not wrapped in anything. So just before showing the popup it's wrapped in, add a completion handler
activity.completionHandler = ^(NSString *activityType, BOOL completed){
[self.popup dismissPopoverAnimated:YES];
};
and you'll be good.
I see the question is quite old, but we've been debugging the same view-controller-not-dismissing issue here and I hope my answer will provide some additional details and a better solution than calling up -dismissPopoverAnimated: manually.
The documentation on the UIActivity is quite sparse and while it hints on the way an implementation should be structured, the question shows it's not so obvious as it could be.
The first thing you should notice is the documentation states you should not be dismissing the view controller manually in anyway. This actually holds true.
What the documentation doesn't say, and what comes as an observable thing when you come across debugging the non-dissmissing-view-controller issue, is iOS will call your -activityViewController method when it needs a reference to the subject view controller. As it turns out, probably only on iPad, iOS doesn't actually store the returned view controller instance anywhere in it's structures and then, when it wants to dismiss the view controller, it merely asks your -activityViewController for the object and then dismisses it. The view controller instantiated in the first call to the method (when it was shown) is thus never dismissed. Ouch. This is the cause of the issue.
How do we properly fix this?
Skimming the UIActivity docs further one may stumble accross the -prepareWithActivityItems: method. The particular hint lies along the following text:
If the implementation of your service requires displaying additional UI to the user, you can use this method to prepare your view controller object and make it available from the activityViewController method.
So, the idea is to instantiate your view controller in the -prepareWithActivityItems: method and tackle it into an instance variable. Then merely return the same instance from your -activityViewController method.
Given this, the view controller will be properly hidden after you call the -activityDidFinish: method w/o any further manual intervention.
Bingo.
NB! Digging this a bit further, the -prepareWithActivityItems: should not instantiate a new view controller each time it's called. If you have previously created one, you should merely re-use it. In our case it happily crashed if we didn't.
I hope this helps someone. :)
I had the same problem. It solved for me saving activityViewController as member and return stored controller. Activity return new object and dismiss invoked on new one.
- (UIViewController *)activityViewController {
if (!self.detaisController) {
// create detailsController
}
return self.detailsController;
}
I pass through the UIActivity to another view then call the following...
[myActivity activityDidFinish:YES];
This works on my device as well as in the simulator. Make sure you're not overriding the activityDidFinish method in your UIActivity .m file as I was doing previously. You can see the code i'm using here.
a workaround is to ask the calling ViewController to perform segue to your destination ViewController via - (void)performActivity although Apple does not recommend to do so.
For example:
- (void)performActivity
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
[self.delegate performSomething]; // (delegate is the calling VC)
[self activityDidFinish: YES];
}
}
- (UIViewController *)activityViewController
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone)
{
UIViewController* vc=XXX;
return vc;
}
else
{
return nil;
}
}
Do you use storyboards? Maybe in your iPad storyboard, the UIActivityIndicatorView doesn't have a check on "Hides When Stopped"?
Hope it helps!
So I had the same problem, I had a custom UIActivity with a custom activityViewController and when it was presented modally it would not dismiss not matter what I tried. The work around I choose to go with so that the experience remained the same to the user was to still use a custom UIActivity but give that activity a delegate. So in my UIActiviy subclass I have the following:
- (void)performActivity
{
if ([self.delegate respondsToSelector:#selector(showViewController)]) {
[self.delegate showViewController];
}
[self activityDidFinish:YES];
}
- (UIViewController *)activityViewController
{
return nil;
}
Then I make the view controller that shows the UIActivityViewController the delegate and it shows the view controller that you would otherwise show in activityViewController in the delegate method.
what about releasing at the end? Using non-arc project!
[progressView release];
Many Users have the same problem as u do! Another solution is:
UIActivityIndicatorView *progress= [[UIActivityIndicatorView alloc] initWithFrame:CGRectMake(125, 50, 30, 30)];
progress.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
[alert addSubview:progress];
[progress startAnimating];
If you are using storyboard be sure that when u click on the activityind. "Hides When Stopped" is clicked!
Hope that helped...

Resources