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...
Related
I've added a sub view like this in the main view:
BTLPXYPad *XYPad = [[BTLPXYPad alloc] initWithFrame: CGRectMake (30, 10, 280, 460)];
[window addSubview:XYPad];
done all my bits that i need to and then removed it using this in the BTLPXYPad class:
[self removeFromSuperview];
What I need is to perform a task once it has gone. I know that with a UIViewController type class I could use viewDidDissapear but I can't seem to find the same thing for a UIView Type. Can anyone help please?
To know when you a view has actually been removed you could implement didMoveToSuperview and check if the superview is now nil
- (void)didMoveToSuperview;
{
[super didMoveToSuperview];
if (!self.superview) {
NSLog(#"Removed from superview");
}
}
[self removeFromSuperview];
What I need is to perform a task once it has gone.
When you say [self removeFromSuperview], it is gone. There is a delay of just one runloop for it to look gone, but removing the view removes the view.
So the solution is just to proceed to your "task once it is gone":
[self removeFromSuperview];
// do your task
If it seems like the "do your task" code is in the wrong place - it isn't. The fact that you need the view to be notified when it is gone shows that your architecture was wrong to start with. A view shouldn't be performing any "task"; it is View in the Model-View-Controller structure. It just displays stuff. Your view controller is Controller; it is the one to do the task.
Nor should the Controller need to consult the view at this point, because the view should not have been storing any important data to begin with. Data is Model, and should already have been retrieve and stored by the Controller before this moment.
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.
the app are not work i am trying to enter a first name but it isnt appears in the main view
#pragma IBAaction
-(IBAction)DoneButton:(id)sender;
{
Customer *newCustomer=[[Customer alloc]init];
newCustomer.firstName=self.firstNameTextField.text;
newCustomer.LastName=self.LastNameTextField.text;
[self.delegate addCustomerViewControllerDidSave:self newCustomer:newCustomer];
[self.tableView reloadData];
}
-(IBAction)CancelButton:(id)sender
{
[self dismissModalViewControllerAnimated:YES];
}
I'm just taking a stab in the dark here given the little information given. But either your Done button isn't wired to the DoneButton: method in interface builder, or self.delegate isn't set like you think it if.
Either way, the debugger or a couple of NSLog statements should help shed some light on the issue.
To some this may sound like a daft question. I've searched around, and found little, mostly because I cannot find the appropriate search terms.
Here what I want to do is:
The application begins at view A.
View A launches view B, and view B launches view C.
Is their a way for view C to return directly back to A without dismissing itself and thus exposing B. For example a main menu button.
You can call popToRootViewControllerAnimated: if you have a UINavigationController. If you specify NO to animate it, then it will just jump back to the root without showing B first.
I have discovered a solution to my problem. Its a bit dirty, (and I''ll probably get shot down in flames for it) but works very well under tests and is very quick to implement. Here's how I did it.
In my app I have a Singleton class called GlobalVars (I use this for storing various global settings). This class holds a boolean called home_pressed and associated accessors (via synthesise). You could also store this value in the application delegate if you wish.
In every view controller with a main menu button, I wire the button to the homePressed IBAction method as follows. First setting the global homePressed boolean to YES, then dismissing the view controller in the usual way, but with NO animation.
-(IBAction) homePressed: (id) sender
{
[GlobalVars _instance].homePressed = YES;
[self dismissModalViewControllerAnimated: NO];
}//end homePressed
In every view controller except the main menu I implement the viewDidAppear method (which gets called when a view re-appears) as follows.
-(void) viewDidAppear: (Bool) animated
{
if ([GlobalVars _instance].homePressed == YES)
{
[self dismissModalViewController: NO];
}
else
{
//put normal view did appear code here/
}
}//end viewDidAppead
In the mainMenu view controller which is the root of the app, I set the global homePressed boolean to NO in its view did appear method as follows
-(void) viewDidAppear: (Bool) animated
{
if ([GlobalVars _instance].homePressed == YES)
{
[GlobalVars _instance].homePressed == NO;
}
else
{
//put normal view did appear code here/
}
}//end viewDidAppear
There, this enables me to go back to the root main menu of my app from any view further down the chain.
I was hoping to avoid this method, but its better than re-implementing my app which is what I'd have to do if I wanted use the UINavigationController solution.
Simple, took me 10 minutes to code in my 9 view app. :)
One final question I do have, would my solution be OK with the HIG?
I have a UIViewController called DebugViewController that contains a UITextView, and a public method called debugPrint which is used to write an NSString into the UITextView and display it.
Is it possible to write into the UITextView before I open the UIViewController, so that when I open it, the text previously written into it is displayed?
In my parent view controllers viewDidLoad method, I'm calling initWithNibName on the DebugViewController as follows
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
I then call debugPrint as follows
[debugViewController debugPrint:#"viewDidLoad"];
And some time later I call the following to open the debugViewController
debugViewController.delegate = self;
debugViewController.modalTransitionStyle = UIModalTransitionStyleCoverVertical;
[self presentModalViewController:debugViewController animated:YES];
However all the text previously written is missing.
Please let me know how I can use a view controllers methods before the view controller displayed to the user.
Thanks,
JustinP
What you are doing is a little non-standard. The danger with that as always is that if you don't really have an expert grasp on what you're doing, you can quickly find yourself in difficulty.
If you want something set before the view is displayed to the user, then the best way to do that is to do it in the viewWillAppear method. Put it there rather than in viewDidLoad because a view might loaded once but appear many times. Where you place it depends on whether the data changes from appearance to appearance.
So, if your data is pretty static and won't change, use the viewDidLoad method.
Assuming that you'll go for the viewWillAppear option, let's do the first step by having an ivar in the view controller:
NSString *myText;
set that after init:
debugViewController = [[DebugViewController alloc] initWithNibName:#"DebugView" bundle:nil];
debugViewController.myText = #"My text here";
then, in debugViewController's viewWillAppear method:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
myTextView.text = myText;
}
The view controller life cycle is complex as you can see from the View Controller Programming Guide for iOS. So I'd say best not stray from the path of least resistance unless you have good reason. That said sometimes the best way to learn is by experimentation.