2

I need to add a UITapGestureRecognizer to the SVProgressHUD. The SVProgressHUD already has the ability to dismiss using -(void) dismiss;. The code for this will dismiss the animation based on seconds.

- (void)dismiss {
for (UIGestureRecognizer *gesture in [[[self class] sharedView] gestureRecognizers]) {
    [[[self class] sharedView] removeGestureRecognizer:gesture];
}

NSDictionary *userInfo = [self notificationUserInfo];
[[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDWillDisappearNotification
                                                    object:nil
                                                  userInfo:userInfo];

self.activityCount = 0;
[UIView animateWithDuration:0.15
                      delay:0
                    options:UIViewAnimationCurveEaseIn | UIViewAnimationOptionAllowUserInteraction
                 animations:^{
                     self.hudView.transform = CGAffineTransformScale(self.hudView.transform, 0.8, 0.8);
                     if(self.isClear) // handle iOS 7 UIToolbar not answer well to hierarchy opacity change
                         self.hudView.alpha = 0;
                     else
                         self.alpha = 0;
                 }
                 completion:^(BOOL finished){
                     if(self.alpha == 0 || self.hudView.alpha == 0) {
                         self.alpha = 0;
                         self.hudView.alpha = 0;

                         [[NSNotificationCenter defaultCenter] removeObserver:self];
                         [self cancelRingLayerAnimation];
                         [self addTapGestureToDismiss];
                         [_hudView removeFromSuperview];
                         _hudView = nil;

                         [_overlayView removeFromSuperview];
                         _overlayView = nil;

                         [_indefiniteAnimatedView removeFromSuperview];
                         _indefiniteAnimatedView = nil;


                         UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil);

                         [[NSNotificationCenter defaultCenter] postNotificationName:SVProgressHUDDidDisappearNotification
                                                                             object:nil
                                                                           userInfo:userInfo];

                         // Tell the rootViewController to update the StatusBar appearance
                         UIViewController *rootController = [[UIApplication sharedApplication] keyWindow].rootViewController;
                         if ([rootController respondsToSelector:@selector(setNeedsStatusBarAppearanceUpdate)]) {
                             [rootController setNeedsStatusBarAppearanceUpdate];
                         }
                         // uncomment to make sure UIWindow is gone from app.windows
                         //NSLog(@"%@", [UIApplication sharedApplication].windows);
                         //NSLog(@"keyWindow = %@", [UIApplication sharedApplication].keyWindow);
                     }
                 }];

}

My thought process is to add the tapGesture code to the dismiss method. This is what I have written so far.

- (void)addTapGestureToDismiss {

// Creation and initializer of the tap gesture
UITapGestureRecognizer *tapRecognizer = [[UITapGestureRecognizer alloc]
                                         initWithTarget:self action:@selector(dismiss)];

// Specify that the gesture must be a single tap
tapRecognizer.numberOfTapsRequired = 1;

// Add the tap gesture recognizer to the view
[[[self class] sharedView] addGestureRecognizer:tapRecognizer];

}

As you can see I'm just initializing the tapGesture. I've run into the issue of placing it in a few places and causing the app to only have one single tap. I've pretty much confused myself in the process. Should I

  • add this gesture to the view?
  • add this gesture to dismiss?
MyloTut
  • 31
  • 1
  • 2
  • After taking a long look at this I found code that shows the image. I implemented some small test code: `if (self.imageView ) { self.dismiss; }` after implementing this test code the HudImage went away instantly after revealing itself. So my question now is, how do I write an if statement for a gesture in _objec-c_. I need to say, dismiss this if the user initiates a tap? – MyloTut Sep 01 '16 at 22:20

5 Answers5

11

Building on Z.Hung's answer, you can make a category on SVProgressHUD so you don't have to repeat this code in every view controller that uses it.

Usage

Just import this category and call

[SVProgressHUD showDismissableErrorWithStatus:@"Error message here"];

Code

@interface SVProgressHUD (Dismissable)

+ (void)showDismissableErrorWithStatus:(NSString*)status;

@end

@implementation SVProgressHUD (Dismissable)

+ (void)showDismissableErrorWithStatus:(NSString*)status {
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleHUDTappedNotification:) name:SVProgressHUDDidReceiveTouchEventNotification object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleHUDDisappearedNotification:) name:SVProgressHUDWillDisappearNotification object:nil];
    [SVProgressHUD showErrorWithStatus: status];
}

#pragma mark - NSNotificationCenter

+ (void)handleHUDTappedNotification: (NSNotification *)notification {
    [SVProgressHUD dismiss];
}

+ (void)handleHUDDisappearedNotification: (NSNotification *)notification {
    [[NSNotificationCenter defaultCenter] removeObserver:self name:SVProgressHUDDidReceiveTouchEventNotification object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self name:SVProgressHUDWillDisappearNotification object:nil];

}

@end

Swift 4

import SVProgressHUD

/// Ref: https://stackoverflow.com/a/41111242/425694
extension SVProgressHUD {

  public static func showDismissableError(with status: String) {
    let nc = NotificationCenter.default
    nc.addObserver(
      self, selector: #selector(hudTapped(_:)),
      name: NSNotification.Name.SVProgressHUDDidReceiveTouchEvent,
      object: nil
    )
    nc.addObserver(
      self, selector: #selector(hudDisappeared(_:)),
      name: NSNotification.Name.SVProgressHUDWillDisappear,
      object: nil
    )
    SVProgressHUD.showError(withStatus: status)
    SVProgressHUD.setDefaultMaskType(.clear)
  }

  @objc
  private static func hudTapped(_ notification: Notification) {
    SVProgressHUD.dismiss()
    SVProgressHUD.setDefaultMaskType(.none)
  }

  @objc
  private static func hudDisappeared(_ notification: Notification) {
    let nc = NotificationCenter.default
    nc.removeObserver(self, name: NSNotification.Name.SVProgressHUDDidReceiveTouchEvent, object: nil)
    nc.removeObserver(self, name: NSNotification.Name.SVProgressHUDWillDisappear, object: nil)
    SVProgressHUD.setDefaultMaskType(.none)
  }

}
MkVal
  • 1,044
  • 1
  • 11
  • 17
charmingToad
  • 1,597
  • 11
  • 18
  • I had to put [SVProgressHUD setDefaultMaskType:SVProgressHUDMaskTypeClear]; after showErrorWithStatus:.., after that it worked like a charm. Thanks! – whtlnv Jan 07 '17 at 19:22
  • That's the best way. First may we need to put SVProgressHUDMaskTypeClear... What happen if I put TypeNone? – Alfredo Luco G Aug 24 '17 at 15:34
2

UPDATE 2.0

After a while I stumbled upon this solution and remembered this question, it works as far as I have tested. Just add a Observer in your viewWillAppear of your ViewController class. No need to modify the library like my previous answer.

-(void)viewWillAppear:(BOOL)animated{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(tapToDismiss:) name:SVProgressHUDDidReceiveTouchEventNotification object:nil];
    //Other initializing
}
-(void)tapToDismiss:(NSNotification *)notification{
    [SVProgressHUD dismiss];
    //maybe other code to stop whatever your progress is
}

This should dismiss the SVProgressHUD even if you have a masktype.

Use this to remove the Observer after you're done (like in the viewDidDisappear) or it will be there throughout the lifetime of the app.

[[NSNotificationCenter defaultCenter] removeObserver:self name:SVProgressHUDDidReceiveTouchEventNotification object:nil];

Credit: http://kevsaidwhat.blogspot.my/2013/06/cancel-svprogresshud-process-by-tapping.html

Zayne ZH
  • 406
  • 3
  • 9
  • I can't change anything about the dismiss method. It needs to stay there or all hudviews will stay on the screen. I need to simply add the feature of tapping the screen to dismiss the HUD if the user chooses to. How would you attach the addTapToDismiss to the view? – MyloTut Sep 01 '16 at 14:14
  • Have you tried to implement the code yet? The dismiss method here is just a local instance within the viewController class, it is not the same method as the dismiss method of the SVProgressHUD. It is simply calling the dismiss of the SVProgressHUD when screen is tapped. I'll edit my answer to include an option – Zayne ZH Sep 01 '16 at 14:25
  • In case I wasn't clear, my suggestion does not involve modifying the original SVProgressHUD library (including the dismiss method you posted). Just add the addTapToDismiss to your viewController class and call it whenever necessary. – Zayne ZH Sep 01 '16 at 14:38
  • it looks like I have some mask types in the code. Any other thoughts? I tried to implement the code in the BaseVC.m that we have and it didn't work. Any other thoughts? – MyloTut Sep 01 '16 at 18:10
  • Check my Updated answer suggesting modifying a library file – Zayne ZH Sep 02 '16 at 02:38
  • That didn't work either. I think it's simply putting in an if..else statement that says if the user taps the screen dismiss the notification image. My confusion now is how to say that in Obj-C. – MyloTut Sep 02 '16 at 16:05
  • Please check update 2.0 part of my answer if you haven't already solved this by yourself – Zayne ZH Sep 08 '16 at 03:06
  • So the code you have is similar to something I implemented with a for--loop. This code cancels the image instantly as it opens. We need it to only cancel when tapped. – MyloTut Sep 08 '16 at 18:50
  • That shouldn't be the case, maybe show your for loop that cancels the image instantly? My guess is you're calling [SVProgressHUD dismiss] right after showing – Zayne ZH Sep 09 '16 at 01:48
  • That didn't work either. Thanks for the consistent help. I'm currently in the .m file of the VC but nothing has happened. I looked into kevsaidwhat and it is weird that it works for everyone except his app. I'll keep looking. – MyloTut Sep 26 '16 at 15:25
2

The complete swift implementation someone maybe find it useful. This is the BaseViewController

func showProgressHud(){
    SVProgressHUD.show()
    SVProgressHUD.setDefaultMaskType(.clear)
}

func hideProgressHud(){
    SVProgressHUD.dismiss()
}


//Hide progress hud on user tap on unresponsive api call

func hideProgressHudOnUserTap(){
    NotificationCenter.default.addObserver(self, selector: #selector(self.tapToDismiss(notification:)), name: NSNotification.Name.SVProgressHUDDidReceiveTouchEvent, object: nil)
}

@objc func tapToDismiss(notification: Notification) {
    hideProgressHud()
}

func removeProgressHudObserver(){
    NotificationCenter.default.removeObserver(self, name: NSNotification.Name.SVProgressHUDDidReceiveTouchEvent, object: nil)
}

Then you call call it in your view controller like this

override func viewWillAppear(_ animated: Bool) {
    hideProgressHudOnUserTap()
}

override func viewDidDisappear(_ animated: Bool) {
    removeProgressHudObserver()
}
Abdullah
  • 227
  • 4
  • 13
0

An easy way to implement this is by:

  1. creating a class that dismisses the HUD:
class HUDManager {
    @objc
    class func dismissHUD() {
        SVProgressHUD.dismiss()
    }
}
  1. Subscribing to the SVProgressHUDDidReceiveTouchEvent notification:
NotificationCenter.default.addObserver(HUDManager.self,
                                       selector: #selector(HUDManager.dismissHUD),
                                       name: .SVProgressHUDDidReceiveTouchEvent,
                                       object: nil)
Eric
  • 16,003
  • 15
  • 87
  • 139
0

Thanks @charmingToad, I would like to post a little more optimized and functional solution on top if here answer.

In my case I need to know the dismiss to cancel the operation if user taps and cancel.

import SVProgressHUD

extension SVProgressHUD {
    private static var dismissCompletion: (() -> ())?
    
    public static func showDismissable(with status: String, tapDismissed: (() -> ())? = nil) {
        dismissCompletion = tapDismissed
        
        let notificationCenter = NotificationCenter.default
        notificationCenter.addObserver(
            self, selector: #selector(hudTapped(_:)),
            name: NSNotification.Name.SVProgressHUDDidReceiveTouchEvent,
            object: nil
        )
        notificationCenter.addObserver(
            self, selector: #selector(hudDisappeared(_:)),
            name: NSNotification.Name.SVProgressHUDWillDisappear,
            object: nil
        )
        SVProgressHUD.show(withStatus: status)
        SVProgressHUD.setDefaultMaskType(.black)
    }

  @objc
  private static func hudTapped(_ notification: Notification) {
    SVProgressHUD.dismiss()
    SVProgressHUD.setDefaultMaskType(.none)
    dismissCompletion?()
  }

  @objc
  private static func hudDisappeared(_ notification: Notification) {
    dismissCompletion = nil
    let notificationCenter = NotificationCenter.default
    notificationCenter.removeObserver(self, name: NSNotification.Name.SVProgressHUDDidReceiveTouchEvent, object: nil)
    notificationCenter.removeObserver(self, name: NSNotification.Name.SVProgressHUDWillDisappear, object: nil)
    SVProgressHUD.setDefaultMaskType(.none)
  }
}
Naveen Shan
  • 9,192
  • 3
  • 29
  • 43