The solution I am about to present in this tutorial, it should be used only if really needed. In many cases, showing a button via a current View Controller is a better option.

Accessing an app's functionality with a global button

I needed to prepare a framework that would provide access to a functionality from every place in my mobile app.

This could be acheived in many ways. For example with a device shake, specific gesture or, simply, via a global button.

I had wanted my framework to be as simple as possible, so I decided that it will be added just one time, in AppDelegate or SceneDelegate.

This effectively forces my 'access button' to be displayed in all available views.

Adding a UIButton to an app's views

So first, I needed to get what view is on top of the app stack. This can be achievied in iOS 13 thanks to this method:

UIApplication.shared.windows

The just-created button can be added to all views just like this:


let button = UIButton()

for window in UIApplication.shared.windows {

window.addSubview(button)

}


Keeping the UIButton's top position

There is yet another important thing. If you want to keep your button on top, you need to change its position:

button.layer.zPosition = +1

If you don’t do this, the button will hide under the presented views and will be neither visible, nor accessible.

This is not all. Before implementing your button in AppDelegate, you should know that using touchUpInside can be tricky.

This is because user gestures are captured by View Controllers. So even if you add a button to the top of a presented view, its actions cannot be captured in AppDelegate (or SceneDelegate).

Both

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool

and

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)

are called before the views are loaded. So adding your button should be delayed if you want to capture its actions. This can be done thanks to the following method:

DispatchQueue.main.async

After its implementation, your button's actions should be accessible just the same way as if you have prepared it in View Controller.

Of course, DispatchQueue can be tricky, so the best way in my opinion is to test your functionality on real devices.

This is it, and thanks for reading!