skip to Main Content

The user can upload data to a server in my Swift 5/Xcode 12.4 app by clicking a button in the navigation bar. While this is happening (asynchronously in a background thread) I’m displaying a custom pop-up and none of the regular buttons (including the buttons in the navigation bar) should be clickable:

static func showIndicator(view: UIView) {
    let parentView = UIView(frame: view.frame)
    parentView.backgroundColor = UIColor.red.withAlphaComponent(0.5) //red only for testing, going to be set to 0.0 later
        
    let indicatorContainer = UIView()
    let width = 80
    let height = 80
        
    indicatorContainer.frame = CGRect(x: 0, y: 0, width: width, height: height)
    indicatorContainer.center = view.center
    indicatorContainer.backgroundColor = UIColor.darkGray.withAlphaComponent(0.97)
    indicatorContainer.layer.cornerRadius = 8
    indicatorContainer.clipsToBounds = true
        
    let indicatorView = UIActivityIndicatorView(style: UIActivityIndicatorView.Style.large)
    indicatorView!.center = CGPoint(x: width/2, y: height/2)
        
    indicatorContainer.addSubview(indicatorView!)
    parentView.addSubview(indicatorContainer)
    view.addSubview(parentView)
    indicatorView!.startAnimating()
}

Called from the UIViewController with:

Toaster.showIndicator(view: self.view)

This makes it impossible to touch the buttons,… in the regular UIView but it doesn’t cover the navigation bar and the user could theoretically start the upload process a second time (amongst other things), which isn’t good. I found two ways to prevent this from happening:

  1. Disable the buttons in the navigation bar temporarily with uploadButton.isEnabled = false. This has the disadvantage that the buttons turn grey.

  2. Cover the whole area, including the navigation bar, with an invisible UIView but this also covers the status bar and I’m not sure if it’s okay to simply access the window like that:

     static func showIndicator(view: UIView) {
         let window = UIApplication.shared.windows.first(where: { $0.isKeyWindow })!
         let parentView = UIView(frame: window.frame)
    
         ...
    
         parentView.addSubview(indicatorContainer)
         window.addSubview(parentView)
         indicatorView!.startAnimating()
     }
    

I know about userInteractionEnabled but you can’t use it for UIBarButtonItems and there are too many other clickable UI elements underneath the pop-up to disable all of them temporarily.

What’s the preferred method for blocking button presses on anything (= underneath the pop-up view) in the app while something else that shouldn’t be interrupted by user interaction is happening?

2

Answers


  1. Excellent question, take a look at view hierarchy

    enter image description here

    navigation bar items are in front of your Toaster, to cover the navigation bar in UIKit, you can add a custom view above the navigation bar using the window property of your app delegate. That’s what most 3rd party ‘toaster’ frameworks such as MBProgressHUD do.

    So the minimal change required to block every touch event behind the toaster is

        // instead of add parent view to view, add it to keyWindow
        // view.addSubview(parentView)
        parentView.tag = 999 // set a tag to remove the view from the window's subviews later
        guard let window = UIApplication.shared.keyWindow else { return }
        window.addSubview(parentView)
        indicatorView.startAnimating()
    
    Login or Signup to reply.
  2. you can use something like this function, which checks all the views in a UIViewController, tries casting them to disable user interaction. you can add as many types as you wish.

    func toggleEditEnabled(editAllowed: Bool) {
        for view in self.view.allSubviews {
            if let button = view as? UIButton {
                button.isUserInteractionEnabled = editAllowed
            } else if let textField = view as? UITextField {
                textField.isUserInteractionEnabled = editAllowed
            }
        }
    }
    

    the function maybe used like this:

    enable interaction: toggleEditEnabled(editAllowed: true)

    disable interaction: toggleEditEnabled(editAllowed: false)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search