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:
-
Disable the buttons in the navigation bar temporarily with
uploadButton.isEnabled = false
. This has the disadvantage that the buttons turn grey. -
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 UIBarButtonItem
s 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
Excellent question, take a look at view hierarchy
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
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.
the function maybe used like this:
enable interaction: toggleEditEnabled(editAllowed: true)
disable interaction: toggleEditEnabled(editAllowed: false)