I have controller (news screen), and i need to detect when user leave it. I tried
- (void)viewWillDisappear:(BOOL)animated
, but the problem is, when user tap share button (share in social networks etc.) that method triggers, but after sharing user is still in news screen, therefore its not work.
I also tried
-(void)willMoveToParentViewController:(UIViewController *)parent {
, but it also trigger when user first enter controller, which is wrong (i need to detect leaving only).
How can i detect when user leave controller, but not trigger when he enter "sharing" pop screen?
These four methods can be used in a view controller's appearance callbacks to determine if it is being presented, dismissed, or added or removed as a child view controller. For example, a view controller can check if it is disappearing because it was dismissed or popped by asking itself in its viewWillDisappear:
method by checking the expression ([self isBeingDismissed] || [self isMovingFromParentViewController]).
- (BOOL)isBeingPresented NS_AVAILABLE_IOS(5_0);
- (BOOL)isBeingDismissed NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingToParentViewController NS_AVAILABLE_IOS(5_0);
- (BOOL)isMovingFromParentViewController NS_AVAILABLE_IOS(5_0);
use isMovingFromParentViewController for your scenario
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
if (self.isMovingFromParentViewController){
}
}
Check this it will help you.
UIActivityViewController *conroller=[[UIActivityViewController alloc] initWithActivityItems:#[#"Hello"] applicationActivities:nil];
You can handle the sharing thing in the completion here
[conroller setCompletionWithItemsHandler:^(NSString *activityType, BOOL completed, NSArray *returnedItems, NSError *activityError){
if(!activityError)
NSLog(#"Shared");
}];
The completion will tell you that the activity was presented so you can handle the activities you want to handle in the completion like this
[self presentViewController:conroller animated:YES completion:^{
NSLog(#"Activity Appeared"); //Same as viewWillDisappear
}];
Hope this helps.
Related
I am creating custom dialogs for my app and some what copying UIAlertController in some aspects. How should I implement the behaviour where when you click any action from alert/dialog the controller is dismissed.
How does Apple do it without making us manually specify for each action handler that it should dismiss the view controller?
I have like them one view controller class:
#interface MyAlertViewController : UIViewController
- (void)addAction:(MyAlertAction *) action;
//...
And one class for the actions:
#interface MyAlertAction : NSObject
- (instancetype)initWithTitle:(nullable NSString *)title handler:(void (^)(MyAlertAction *action))handler;
EDIT: How I did it taking in accord the answer feedback:
//MYAlertViewController.m
- (void)viewDidLoad {
for (int i = 0; i < self.actions.count; i++) {
MYAlertAction *action = self.actions[i];
button = [[UIButton alloc] initWithFrame:CGRectZero];
button.tag = i;//this here is how I link the button to the action
[button addTarget:self action:#selector(executeAction:) forControlEvents:UIControlEventTouchUpInside];
[actionStackView addArrangedSubview:button];
[self.actionsStackView addArrangedSubview:actionLayout];
}
}
- (void)executeAction:(UIButton *) sender{
[self dismissViewControllerAnimated:YES completion:^{
//this is where the button tag comes in handy
MYAlertAction *actionToExecute = self.actions[sender.tag];
actionToExecute.actionHandler();
}];
}
How does Apple do it without making us manually specify for each action handler that it should dismiss the view controller?
You are confusing two different things:
The UIAlertAction's last initialization parameter, the handler parameter, which you get to set from outside, and which is to run after the button is tapped and after the alert has been dismissed. It is a block.
The actual button's action, which the client can't set or see. It is configured by the alert controller. It is a selector.
So now, you play the role of the UIAlertController. In your
- (instancetype)initWithTitle:(nullable NSString *)title handler:(void (^)(MyAlertAction *action))handler;
the client hands you the first action I mentioned, the block, and you store it for later execution. But the second action, the button action, the selector, is entirely up to you as you create the button in response to this call.
So as you configure the button, just configure it with a target/action pair that calls into a method of your view controller, just as for any button. In method, when called, the view controller dismisses itself, and in the completion handler of the dismissal, calls the block.
I have a routine in my iOS program that imports and manipulates a file from Dropbox. This can take some time (5-10 seconds) and it doesn't make sense to return the user to the normal UI while it's doing it, so I want to present a view letting the user know what the progress is.
From one VC, I use Dropbox's drop-in file picker, then load up a presented (modal VC) thus:
ZSImportVC *importVC = [[ZSImportVC alloc] init];
importVC.results = results;
[importVC setModalPresentationStyle:UIModalPresentationFormSheet];
[self presentViewController:importVC animated:YES completion:^{
[self performFetch];
}];
The VC (a bog-standard UIViewController), has a UILabel property:
#property (weak, nonatomic) IBOutlet UILabel *statusMessage;
In viewWillAppear: I can set the text of this label without any problem. The thing is, I want to keep changing this text as the process of manipulating the file continues.
The method that manipulates the file is called from viewDidAppear:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self processImport];
}
However, within the processImport method, the following has no effect:
self.statusMessage.text = #"Some text to update the user.";
So I created a method:
- (IBAction)updateStatus:(NSString *)message
{
[self.statusMessage setText:message];
NSLog(#"%#", message);
}
just to check what's going on. The NSLog shows that the method is being called okay, but the label text doesn't change. I tried adding:
[self.statusMessage setNeedsDisplay];
to the method, but that didn't help. I'm not using any private queues or background threads. I read somewhere that using NSNotification helps, so I tried adding this to viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateStatus:) name:#"updateStatus" object:nil];
Then changed the called method to:
- (void)updateStatus:(NSNotification *)message
{
[self.statusMessage setText:message.object];
NSLog(#"%#", message.object);
}
and called this from the main method with:
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateStatus" object:#"Retrieving file from Dropbox" userInfo:nil];
I could see from the console messages that the updateStatus method is getting called, but still the text doesn't change. Clearly I'm missing something here. Any thoughts?
Check your ZSImportVC instances on Debug perspective.
I think u are sending [self.statusMessage setText:message.object];
to another instance, that it is not shown on the screen.
I mean,
put a debug point here:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self processImport]; /* Debug point here*/
}
and check what´s the hexadecimal direction for self.
Now , do the same here:
- (void)updateStatus:(NSNotification *)message
{
[self.statusMessage setText:message.object]; /* Debug point here*/
NSLog(#"%#", message.object);
}
Btw, I had some problems with
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateStatus" object:#"Retrieving file from Dropbox" userInfo:nil];
because the instance that receive this notification was not the correct.
I fixed that calling a normal method, but u maybe could not do that.
Sorry for my english :S
I am using a uipopover to present a mini number pad to the user when they enter a textfield on my main view controller.
when they enter numbers using the number pad, i save the entry into a nsstring property that I've named keypadvalue.
there is an unwind segue wired to a done button on the popover which fires the following code.
- (IBAction)doneWithKeyboard:(UIStoryboardSegue *)segue
{
NSLog(#"unwind");
if ([segue.sourceViewController isKindOfClass:[KeyPopupViewController class]])
{
KeyPopupViewController *popOver2 = segue.sourceViewController;
activeField.text =popOver2.keypadValue;
}
}
the activetextfield on my main view controller then gets updated to the kepadvalue, and this all works fine.
my problem now is that i want the activetextfield to update the same way if the user presses outside the uipopover, and it dismisses without firing the unwind segue.
i thought i might use the following to perform the update when the popover dismisses
-(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
activeField.text = controller.keypadValue;
return YES;
}
unfortunately despite multiple attempts i can't get the property to return a value it is always null even though the method fires as expected.
how should i recover the property value from the popover using this or another method?
i am obviously doing something wrong
can anyone advise
thanks
It should help:
-(BOOL)popoverControllerShouldDismissPopover:(UIPopoverController *)popoverController
{
[self.view endEditing:YES];
activeField.text = controller.keypadValue;
return YES;
}
I'm using a UIWebView, and it pops an alert when a button is clicked(from javascript). There is another button (in native side), which closes controller, so deallocs also UIWebView.
The problem is, if I touch the button in UIWebView, and touch to close button before alert is populated, my controller and UIWebView are deallocated, but alert remains on screen. Then if I click any button on alert, application crashes and gives following error:
[UIWebView modalView:didDismissWithButtonIndex:]: message sent to deallocated instance
And this method is called from private method
[UIModalView(Private) _popoutAnimationDidStop:finished:]
I'm using ARC, and my dealloc is like this:
- (void)dealloc {
[_myWebView stopLoading];
_myWebView.delegate = nil;
_myWebView = nil;
}
But this does not solve my problem because I think UIModalView has a reference of my webview as a delegate, and I could not set it to nil because its private.
How can I solve it?
Regards
Find a way to set the delegate on UIAlertView to nil before deallocating UIWebView.
This is an Apple bug in their handling of the alert view. Open a bug report.
In the meantime, here are some workarounds:
Create a category on UIAlertView:
#interface UIAlertView (QuickDismiss) #end
#implementation UIAlertView (QuickDismiss)
- (void)__quickDismiss
{
[self dismissWithClickedButtonIndex:self.cancelButtonIndex animated:NO];
}
#end
Now, in your view controller's dealloc method, call this:
[[UIApplication sharedApplication] sendAction:#selector(__quickDismiss) to:nil from:nil forEvent:nil];
This will dismiss all alert views that are currently open, including the ones displayed by your web view.
If that does not work, you can always iterate all subviews of all UIApplication.sharedApplication.windows objects, checking whether [view.class.description hasPrefix:#"UIAlertView"] is true, and dismissing that. This is a less elegant method than the previous one, and should be last resort.
Good luck.
Finally, I find a great solution which actully works. I use method swizzle to hook UIAlertView Delegate function - (void)didPresentAlertView:(UIAlertView *)alertView
- (void)didPresentAlertView:(UIAlertView *)alertView
{
if ([self.delegate respondsToSelector:#selector(didPresentAlertView:)]) {
[self.delegate didPresentAlertView:alertView];
}
if ([self.delegate isKindOfClass: [UIWebView class]]) {
uiWebView = self.delegate;
}
}
I just retain UIWebView instance in this function so that the UIWebView instance as UIAlertView's delegate will not be released before UIAlertView instance being released.
I've read and read on SO about this, and I just can't seem to find anything that matches my situation.
I've got MBProgressHUD loading when the view appears, as my app immediately goes to grab some webservice data. My problem is the back button on my navigationcontroller is unresponsive while the HUD is displayed (and therefore while the app gets its data). I want the user to be able to tap to dismiss (or to be able to hit the back button in the worst case) to get the heck out, if it's an endless wait. Here's my code that runs as soon as the view appears:
#ifdef __BLOCKS__
MBProgressHUD *hud = [MBProgressHUD showHUDAddedTo:self.navigationController.view animated:YES];
hud.labelText = #"Loading";
hud.dimBackground = NO;
hud.userInteractionEnabled = YES;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
// Do a task in the background
NSString *strURL = #"http://WEBSERVICE_URL_HERE";
//All the usual stuff to get the data from the service in here
NSDictionary* responseDict = [json objectForKey:#"data"]; // Get the dictionary
NSArray* resultsArray = [responseDict objectForKey:#"key"];
// Hide the HUD in the main tread
dispatch_async(dispatch_get_main_queue(), ^{
for (NSDictionary* internalDict in resultsArray)
{
for (NSString *key in [internalDict allKeys])
{//Parse everything and display the results
}
}
[MBProgressHUD hideHUDForView:self.navigationController.view animated:YES];
});
});
#endif
Leaving out all the gibberish about parsing the JSON. This all works fine, and the HUD dismisses after the data shows up and gets displayed. How in the world can I enable a way to stop all this on a tap and get back to the (blank) interface? GestureRecognizer? Would I set that up in the MBProgressHUD class? So frustrated...
Kindest thanks for any help. My apologies for the long post. And for my ugly code...
No need to extend MBProgressHUD. Simply add an UITapGestureRecognizer to it.
ViewDidLoad
:
MBProgressHUD *HUD = [MBProgressHUD showHUDAddedTo:self.view animated:NO];
HUD.mode = MBProgressHUDModeAnnularDeterminate;
UITapGestureRecognizer *HUDSingleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(singleTap:)];
[HUD addGestureRecognizer:HUDSingleTap];
And then:
-(void)singleTap:(UITapGestureRecognizer*)sender
{
//do what you need.
}
The MBProgressHUD is just a view with a custom drawing to indicate the current progress, which means it is not responsible for any of your app's logic. If you have a long running operation which needs to be canceled at some point, you have to implement this yourself.
The most elegant solution is to extend the MBProgressHUD. You can either draw a custom area which plays the role of a button, add a button programmatically or just wait for a tap event on the whole view. Then you can call a delegate method whenever that button or the view is tapped.
It can look like this:
// MBProgressHUD.h
#protocol MBProgressHUDDelegate <NSObject>
- (void)hudViewWasTapped; // or any other name
#end
// MBProgressHUD.m
// Either this, or some selector you set up for a gesture recognizer
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
if ([self.delegate respondsToSelector:#selector(hudViewWasTapped)]) {
[self.delegate performSelector:#selector(hudViewWasTapped)];
}
}
you have to set your view controller as the delegate for theMBProgressHUD and act accordingly.
Let me know if you need more clarification on this :)
To have extra information:
You could create contentView in your view
And simply show the hud in your contentView (not in your self.view or self.navigationController.view)
in this way your navigationBar's view will not be responsible for your hudView. So, you can go back from your navigationController's view to previous page.