简体   繁体   中英

Dismiss SVProgressHUD with Tap Gesture

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?

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)
  }

}

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

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()
}

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)

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)
  }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM