Newbee
Newbee

Reputation: 3301

How to add a UIView over the Keyboard - iOS

I have been trying to display a toast message on iOS. What I did was when any notification comes, just I took the navigation controller view and added a subview for my toast message and displayed.

    UIView *top_view = self.navigationController.view;
    [top_view showToast:string];

Everything works fine. However my toast view is not adding over the keyboard(if the keyboard is at the front). Any idea what could be the problem... Little helps may save my time... Thanx..

Upvotes: 4

Views: 8546

Answers (7)

abhimuralidharan
abhimuralidharan

Reputation: 5939

Try to add the view as a subview of the following class. This code snippet works for iOS 14 and above. Not sure about older versions. Reference: Toaster Github repo

Use it like: ToastWindow.shared.addSubView(/your_view/)

public final class ToastWindow: UIWindow {
public static let shared = ToastWindow(frame: UIScreen.main.bounds, mainWindow: UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow } )

override public var rootViewController: UIViewController? {
    get {
        guard !self.isShowing else {
            isShowing = false
            return nil
        }
        guard let firstWindow = UIApplication.shared.delegate?.window else { return nil }
        return firstWindow is ToastWindow ? nil : firstWindow?.rootViewController
    }
    set { /* Do nothing */ }
}

override public var isHidden: Bool {
    willSet { isShowing = true }
    didSet { isShowing = false }
}

/// Will not return `rootViewController` while this value is `true`. Needed for iOS 13.
private var isShowing = false

/// Returns original subviews. `ToastWindow` overrides `addSubview()` to add a subview to the
/// top window instead itself.
private var originalSubviews = NSPointerArray.weakObjects()
private weak var mainWindow: UIWindow?

// MARK: - Init
public init(frame: CGRect, mainWindow: UIWindow?) {
    super.init(frame: frame)
    self.mainWindow = mainWindow
    self.isUserInteractionEnabled = false
    self.gestureRecognizers = nil
    self.windowLevel = .init(rawValue: .greatestFiniteMagnitude)
    let keyboardWillShowName = UIWindow.keyboardWillShowNotification
    let keyboardDidHideName = UIWindow.keyboardDidHideNotification
    self.backgroundColor = .clear
    self.isHidden = false

    NotificationCenter.default.addObserver( self, selector: #selector(self.keyboardWillShow),
                                            name: keyboardWillShowName,
                                            object: nil )
    NotificationCenter.default.addObserver( self, selector: #selector(self.keyboardDidHide),
                                            name: keyboardDidHideName,
                                            object: nil )
}

required public init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented: please use ToastWindow.shared")
}

override public func addSubview(_ view: UIView) {
    super.addSubview(view)
    self.originalSubviews.addPointer(Unmanaged.passUnretained(view).toOpaque())
    self.topWindow()?.addSubview(view)
}

public override func becomeKey() {
    super.becomeKey()
    mainWindow?.makeKey()
}


// MARK: - Keyboard methods
@objc private func keyboardWillShow() {
    guard let topWindow = self.topWindow(),
          let subviews = self.originalSubviews.allObjects as? [UIView] else { return }
    for subview in subviews {
        topWindow.addSubview(subview)
    }
}

@objc private func keyboardDidHide() {
    guard let subviews = self.originalSubviews.allObjects as? [UIView] else { return }
    for subview in subviews {
        super.addSubview(subview)
    }
}

/// Returns top window that isn't self
private func topWindow() -> UIWindow? {
    if let window = UIApplication.shared.windows.last(where: {
        ToastWindowKeyboardObserver.shared.didKeyboardShow || $0.isOpaque
    }), window !== self {
        return window
    }
    return nil
}
}

final fileprivate class ToastWindowKeyboardObserver {
static let shared = ToastWindowKeyboardObserver()
private(set) var didKeyboardShow: Bool = false
private(set) var keyboardHeight = 0.0

private init() {
    let keyboardWillShowName = UIWindow.keyboardWillShowNotification
    let keyboardDidHideName = UIWindow.keyboardDidHideNotification

    NotificationCenter.default.addObserver( self, selector: #selector(keyboardWillShow),
                                            name: keyboardWillShowName,
                                            object: nil )
    NotificationCenter.default.addObserver( self, selector: #selector(keyboardDidHide),
                                            name: keyboardDidHideName,
                                            object: nil )
}

@objc func keyboardWillShow(_ notification: Notification) {
    if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
        let keyboardRectangle = keyboardFrame.cgRectValue
        keyboardHeight = keyboardRectangle.height
    }
    didKeyboardShow = true
}

@objc private func keyboardDidHide() {
    didKeyboardShow = false
}
}

Upvotes: 0

zhu.bingyi
zhu.bingyi

Reputation: 11

Update for Swift3

UIApplication.shared.windows.last

Upvotes: 1

Francesco
Francesco

Reputation: 5153

Another way is to add a custom UIWindow, then setting it's WindowLevel to +1 of the last window.

Something like this

NSArray *windows = [[UIApplication sharedApplication] windows];  
UIWindow *lastWindow = (UIWindow *)[windows lastObject];  
myWindow.windowLevel = lastWindow.windowLevel + 1;

Take a look to this topic https://forums.developer.apple.com/thread/16375

Upvotes: 2

king wang
king wang

Reputation: 568

in iOS9 the answer by Adithya is not work,

UIWindow *window = [UIApplication sharedApplication].windows.lastObject;

work well

Upvotes: 0

Yunus Nedim Mehel
Yunus Nedim Mehel

Reputation: 12369

You have to add your subview to:

UIWindow *window = [UIApplication sharedApplication].windows.lastObject;

which is on top of the keyboard.

Upvotes: 8

Adithya
Adithya

Reputation: 4705

You can display the toast by adding subview to your main window.

UIWindow *toastDisplaywindow = [[[UIApplication sharedApplication] delegate] window];;
for (UIWindow *testWindow in [[UIApplication sharedApplication] windows]) 
{
   if (![[testWindow class] isEqual:[UIWindow class]]) 
    {
       self.toastDisplaywindow = testWindow;
       break;
    }
}
[toastDisplaywindow showToast:string];

If a keyboard is being displayed, it will be displayed as a separate window, above your usual main window. Hence a check made to find out if the keyboard is being displayed. If it is, then add the toast message on that window, else on the main window.

I found another method in this link, using which you can directly get to the UIView of the keyboard (If required).

Upvotes: 9

iNeal
iNeal

Reputation: 1729

Generally keyboard view is not part of your main window. it appears with new window when you get focused in any text field.

Try the following code to access your keyboard view.

[[[UIApplication sharedApplication] windows] objectAtIndex:1]

Remember, this will only work when you have keyboard on your screen.

Upvotes: 3

Related Questions