Swift, UISceneDelegate UIScene overlay keyboard - ios

I'm porting my app from non-SceneDelegate to become an app that supports SceneDelegate...
I've run into a problem where my custom "AlertView", which used to overlay the keyboard, no longer appears to overlay the keyboard, and instead appears "behind" the keyboard. Even with windowLevel set properly
I'm using the code block to present my custom "scene", which is effectively a blank UIWindow where I embed a customized "AlertView"
let sceneDelegate = UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate
let controller = AlertViewController()
controller.alertView = alertView
// create the uiwindow
sceneDelegate?.alertWindow = UIWindow(frame: (view.window?.frame)!)
sceneDelegate?.alertWindow?.windowScene = self.view.window?.windowScene
sceneDelegate?.alertWindow?.windowLevel = UIWindow.Level.alert + 1
sceneDelegate?.alertWindow?.backgroundColor = .red.withAlphaComponent(0.5)
sceneDelegate?.alertWindow?.rootViewController = controller
sceneDelegate?.alertWindow?.isHidden = false
It appears that calling
sceneDelegate?.alertWindow?.makeKeyAndVisible()
Dismisses the keyboard (and the newly created UIWindow sits behind the keyboard), whereas calling
sceneDelegate?.alertWindow?.isHidden = false
Still presents the UIWindow but does not dismiss the keyboard.
Am I missing something here in my setup to make my UIWindow appear "above" the keyboard, without dismissing the keyboard?
Does Apple allow us to overlay on top of the keyboard anymore? Or was that only available with UIWindow?
Thank you.
As you can see, the "red" background I added for testing should appear above the keyboard as it did when working with UIApplication.shared.window
https://i.stack.imgur.com/NjpWX.jpg

Related

How to change UIWindow background color through a view controller

This question is really similar to this: How to set an app's UIWindow color when calling it from ViewController. However, the solutions there aren't working.
Ever since apple changed the window to be located in the scene delegate (instead of the app delegate) I haven't been able to change the window (UIWindow) background color outside of scene delegate (using the methods in the article above). Is there any way to either call a function inside of Scene Delegate (through a view controller) or to change the window background color in a view controller. The reason I need this feature is because I handle my themes inside of the app, and when the user changes the theme I need the UIWindow background color to also change (for all of my app) for any cases where the UIWindow color shows (ex: presenting a view controller). A solution which I've tried is:
if let window = UIApplication.shared.delegate?.window as? UIWindow {
window.backgroundColor = .red
}
But it doesn't work for me.
You can change it in any controller like this
override func viewDidAppear(_ animated: Bool) {
view.window?.backgroundColor = .red
}

Swift Modal View Controller with transparent background [duplicate]

This question already has answers here:
Transparent background for modally presented viewcontroller
(5 answers)
Closed 7 years ago.
I know this topic is quite popular, but I'm a little iniciate problem in a programming language, the fact is that I still do not understand where I put the code. Well, I'll tell the whole case:
I'm trying to make a modal Swift in a little different from normal: By clicking on a button, the ViewController is displayed (following modal type) on the screen, but with transparent background. Only the blue View with label will be displayed. When this ViewController is presented, it is with transparent background, but as soon as it completes the transition, it will stay with the black background. Already deactivated the opaque option, and tested some options, but nothing this troubleshooting.
Some can help me?
The video is a test in the simulator on the case (https://www.youtube.com/watch?v=wT8Uwmq9yqY).
I'm starting with swift, and I'm still pretty lost with how to program in Xcode, I read an answer to a question that has the following code to solve this:
self.presentingViewController.providesPresentationContextTransitionStyle = YES;
self.presentingViewController.definesPresentationContext = YES;
modal.modalPresentationStyle = UIModalPresentationOverCurrentContext;
Where do I put this code?
You can do it like this:
In your main view controller:
func showModal() {
let modalViewController = ModalViewController()
modalViewController.modalPresentationStyle = .overCurrentContext
presentViewController(modalViewController, animated: true, completion: nil)
}
In your modal view controller:
class ModalViewController: UIViewController {
override func viewDidLoad() {
view.backgroundColor = UIColor.clearColor()
view.opaque = false
}
}
If you are working with a storyboard:
Just add a Storyboard Segue with Kind set to Present Modally to your modal view controller and on this view controller set the following values:
Background = Clear Color
Drawing = Uncheck the Opaque checkbox
Presentation = Over Current Context
As Crashalot pointed out in his comment: Make sure the segue only uses Default for both Presentation and Transition. Using Current Context for Presentation makes the modal turn black instead of remaining transparent.

presenting a modal view controller while the keyboard is active

So I basically have a form, consisting of several text fields. The user types into the fields as usual. But the user also has the option of double-tapping a text field, which presents a modal view controller, allowing the user to choose from a number of options relating to that field.
Can I somehow present the modal "over" the keyboard, such that when it is dismissed, the keyboard is still active for the field that had been first responder before I presented the modal?
Right now, the keyboard dismisses while the modal appears, and reappears as the modal is dismissed. It looks clunky to me, and distracting. Would love to streamline it, and reduce the amount of animation onscreen.
Edit: I've updated this answer for iOS 12 and Swift. The revised example project (containing new Swift and updated Objective-C implementations) is here.
You can create a new UIWindow and place that over the default window while hiding the keyboard's window.
I have an example project on Github here, but the basic process is below.
Create a new UIViewController class for your modal view. I called mine OverlayViewController. Set up the corresponding view as you wish. Per your question you need to pass back some options, so I made a delegate protocol OverlayViewController and will make the primary window's root view controller (class ViewController) our delegate.
protocol OverlayViewControllerDelegate: class {
func optionChosen(option: YourOptionsEnum)
}
Add some supporting properties to our original view controller.
class ViewController: UIViewController {
/// The text field that responds to a double-tap.
#IBOutlet private weak var firstField: UITextField!
/// A simple label that shows we received a message back from the overlay.
#IBOutlet private weak var label: UILabel!
/// The window that will appear over our existing one.
private var overlayWindow: UIWindow?
Add a UITapGestureRecognizer to your UITextField.
override func viewDidLoad() {
super.viewDidLoad()
// Set up gesture recognizer
let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleDoubleTap))
doubleTapRecognizer.numberOfTapsRequired = 2
doubleTapRecognizer.delegate = self
firstField.addGestureRecognizer(doubleTapRecognizer)
firstField.becomeFirstResponder()
}
UITextField has a built-in gesture recognizer, so we need to allow multiple UIGestureRecognizers to operate simultaneously.
extension ViewController: UIGestureRecognizerDelegate {
// Our gesture recognizer clashes with UITextField's.
// Need to allow both to work simultaneously.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
}
This is the interesting part. When the gesture recognizer is triggered, create the new UIWindow, assign your OverlayViewController as the root view controller, and show it. Note that we set the window level to UIWindowLevelAlert so it will appear in front. However, the keyboard will still be in front despite the alert window level, so we have to manually hide its window, too.
It is important to not set the new UIWindow as key or to change the first responder from the UITextField or the keyboard will be dismissed.
Previously (before iOS 10?) we could get away with overlayWindow.makeKeyAndVisible(), but now setting it as key will dismiss the keyboard. Also, the keyboard's window now has a non-standard UIWindow.Level value that is in front of every publicly defined value. I've worked around that by finding the keyboard's window in the hierarchy and hiding it instead.
#objc func handleDoubleTap() {
// Prepare the overlay window
guard let overlayFrame = view?.window?.frame else { return }
overlayWindow = UIWindow(frame: overlayFrame)
overlayWindow?.windowLevel = .alert
let overlayVC = OverlayViewController.init(nibName: "OverlayViewController", bundle: nil)
overlayWindow?.rootViewController = overlayVC
overlayVC.delegate = self
// The keyboard's window always appears to be the last in the hierarchy.
let keyboardWindow = UIApplication.shared.windows.last
keyboardWindow?.isHidden = true
}
The overlay window is now the original window. The user can now select whatever options you built into the overlay view. After your user selects an option, your delegate should take whatever action you intend and then dismiss the overlay window and show the keyboard again.
func optionChosen(option: YourOptionsEnum) {
// Your code goes here. Take action based on the option chosen.
// ...
// Dismiss the overlay and show the keyboard
overlayWindow = nil;
UIApplication.shared.windows.last?.isHidden = false
}
The overlay window should disappear, and your original window should appear with the keyboard in the same position as before.
I can't try this right now, but have implemented similar for other purposes. In the action for presenting the modal controller, I assume gesture recognizer or delegate method, first take a screenshot and place it in an imageView over the current subviews. Later when returning, simply remove the imageView.
Might sound crazy but I remember having done this for a transition where the keyboard moving during the transition caused similar clunky behavior. It was not difficult to implement at all.
If you have trouble trying it, perhaps someone will provide some code. I can reference my own work later and add an example, but not now.
#Rob Bajorek's answer is excellent.
For iOS 9,10 there are small changes.
Instead of the code:
[self.overlayWindow setWindowLevel:UIWindowLevelAlert];
[self.overlayWindow makeKeyAndVisible];
Put the following code:
NSArray *windows = [[UIApplication sharedApplication] windows];
UIWindow *lastWindow = (UIWindow *)[windows lastObject];
[self.overlayWindow setWindowLevel:lastWindow.windowLevel + 1];
[self.overlayWindow setHidden:NO];
In order to keyboard to visible any of text accepting fields such UITextField or UITextView or UISearchBar should be the first responder and they should be visible in the view. Meaning responding view should be in the top level hierarchy in the window.
If you don't need this effect, Instead of presenting a ViewController you can add ViewController.view as a subview of your self.view with animation.
You have access to the frame of the keyboard in iOS.
You need to implement code to listen to the keyboard notifications (like UIKeyboardWillShowNotification and UIKeyboardWillChangeFrameNotification). The notification will send you informations about the frame of the keyboard.
Giva a look to the description of the "Keyboard Notification User Info Keys" in the windows reference.
You'll find useful for you purpose:
UIKeyboardBoundsUserInfoKey The key for an NSValue object containing a CGRect that identifies the bounds rectangle of the
keyboard in window coordinates. This value is sufficient for obtaining
the size of the keyboard. If you want to get the origin of the
keyboard on the screen (before or after animation) use the values
obtained from the user info dictionary through the
UIKeyboardCenterBeginUserInfoKey or UIKeyboardCenterEndUserInfoKey
constants.
With the information of the keyboard frame you can show there you modal view.
Just add an tap gesture in your textfield and a UITextfield *flagTextfield;
UITapGestureRecognizer* doubleTap = [[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(DoubleTapMethod:)];
doubleTap.numberOfTapsRequired = 2;
[self.txtTest addGestureRecognizer:doubleTap];
-(void)DoubleTapMethod:(UITapGestureRecognizer *)gesture
{
[flagTextfield resignFirstResponder];
NSLog(#"DoubleTap detected");
//Set your logic on double tap of Textfield...
//presents a modal view controller
}
- (void)textFieldDidBeginEditing:(UITextField *)textField{
flagTextfield = textfield;
}

Force view controller to reload to refresh UIAppearance changes

I have been searching for quite a while and can't find an answer. I am working on an iOS app and have a modal settings page that appears on the tap of a button and returns with a segue.
One of the options I would like to implement is a color scheme setting. I really want to avoid manually changing the color for every element on the page.
Apple has a UIAppearance protocol for this sort of thing (so I can set the text color of all buttons, etc.
Their documentation says:
Note: iOS applies appearance changes when a view enters a window, it doesn’t change the appearance of a view that’s already in a window. To change the appearance of a view that’s currently in a window, remove the view from the view hierarchy and then put it back.
My question is how to do this. I have tried calling viewWillAppear and setNeedsDisplay without luck.
Try to use this snippet :
NSArray *windows = [UIApplication sharedApplication].windows;
for (UIWindow *window in windows) {
for (UIView *view in window.subviews) {
[view removeFromSuperview];
[window addSubview:view];
}
}
http://snipplr.com/view/75259/refresh-uiappearance-after-application-loaded/
It works perfect for me after changing app theme using UIAppearance
Please note that the top answer will have adverse effects on your system keyboard behavior.
It turns out that iOS creates a new system window with UITextEffectsWindow class under the hood whenever the keyboard is displayed. If you remove it, your keyboard behavior may be negatively affected. For example, the input accessory views will be detached from the keyboard and will not be visible, except for brief flashes in the navigation controllers.
You can workaround this issue by using an additional check, like so:
for window in UIApplication.shared.windows {
// Whenever a system keyboard is shown, a special internal window is created in application
// window list of type UITextEffectsWindow. This kind of window cannot be safely removed without
// having an adverse effect on keyboard behavior. For example, an input accessory view is
// disconnected from the keyboard. Therefore, a check for this class is needed. In case this class
// that is indernal is removed from the iOS SDK in future, there is a "fallback" class check on
// NSString class that always fails.
if !window.isKind(of: NSClassFromString("UITextEffectsWindow") ?? NSString.classForCoder()) {
window.subviews.forEach {
$0.removeFromSuperview()
window.addSubview($0)
}
}
}
Note that the UITextEffectsWindow is internal and may change in the future. This is why I do not unwrap the variable using ! but provide a fallback negative NSString class instead (no type of window is of NSString class).
Note: For simple apps, you can probably live by using UIApplication.shared.keyWindow for the workaround.
Specifically, to get the current view and it's superview, try:
UIView *currentview = self.window.rootViewController.view;
UIView *superview = currentview.superview;
[currentview removeFromSuperview];
[superview addSubview:currentview];
Works for me.
For Swift:
let windows = UIApplication.sharedApplication().windows
for window in windows {
for view in window.subviews {
view.removeFromSuperview()
window.addSubview(view)
}
}
For Swift 3.0.2:
for window in UIApplication.shared.windows {
for view in window.subviews {
view.removeFromSuperview()
window.addSubview(view)
}
// update the status bar if you change the appearance of it.
window.rootViewController?.setNeedsStatusBarAppearanceUpdate()
}
Here's a Swift 5 one-liner:
UIApplication.shared.windows.forEach { $0.subviews.forEach { $0.removeFromSuperview(); self.window?.addSubview($0) }}
Try
[self.yourView removeFromSuperView];
[self addSubView:yourView];
For swift 4:
let windows = UIApplication.shared.windows
for window in windows {
for view in window.subviews {
view.removeFromSuperview()
window.addSubview(view)
}
}
The most answers are very good and perfect for changing language from LTR to RTL but sometimes tab bar navigation titles and navigation bar titles will not get translated. I fixed the problem with the following code
if let app = UIApplication.shared.delegate as? AppDelegate, let window = app.window {
window.rootViewController = TabNavigationController()
let tab = window.rootViewController as? UITabBarController
tab?.selectedIndex = 3
window.makeKeyAndVisible()
}
Objective c
self.view.window.overrideUserInterfaceStyle = UIUserInterfaceStyleDark;
I use this code if I want change overrideUserInterfaceStyle in all view controllers

Make custom keyboard non-movable

We created a custom numeric keypad for iPad. On our testers iPad this keyboard was suddenly moved out of place and it took us quite a while to figure out that it is possible to move that custom keyboard by start dragging at the location where the "Keyboard Button" would normally be located.
The customers will have very hard times to move it back in case they accidentally moved it. As it makes no sense to move the keyboard on that specific input screen I would rather prefer to prevent the keyboard from moving instead of painting some kind of handle that makes visible to the users that the keyboard can be moved. (This is a special input screen for just editing one single numeric value. The keyboard is like part of the layout and is always visible on this screen.)
I tried hard but could not find a way to prevent the keyboard from moving when dragged on this specific place. Even all my dirty ideas like removing possibly preexisting GestureRecognizers (there were none) or placing my own button in front did not help.
Edit:
The keyboard is even movable in the simplest possible custom keyboard app written in Monotouch I can think of. Did I missed something?
using System;
using MonoTouch.Foundation;
using MonoTouch.UIKit;
namespace KeyboardTest
{
[Register ("AppDelegate")]
public partial class AppDelegate : UIApplicationDelegate
{
private UIWindow window;
private UIViewController viewController;
private UIViewController keyboardViewController;
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
window = new UIWindow (UIScreen.MainScreen.Bounds);
// Create a red dummy keyboard
keyboardViewController = new UIViewController();
keyboardViewController.View.BackgroundColor = UIColor.Red;
// Create a textfield and assign our beautiful red keyboard as its InputView
UITextField textField = new UITextField();
textField.BorderStyle = UITextBorderStyle.RoundedRect;
textField.Frame = new System.Drawing.RectangleF(44, 44, 200, 44);
textField.InputView = keyboardViewController.View;
// create a rootview controller and add our textfield
viewController = new UIViewController();
viewController.View.AddSubview(textField);
window.RootViewController = viewController;
window.MakeKeyAndVisible ();
return true;
}
}
}
For what you explain, I´m guessing that you have your keyboard as a subview of your main view. Instead, I would set it as a inputView of the UItextFields and then make the first of your textFields be the firs responder on viewDidLoad. Something like:
-(void)viewDidLoad{
[super viewDidLoad];
CustomkeyPad *keypad=[[CustomKeyPad alloc]init]; // initWithFrame would probably be better
self.textField.delegate=self;
self.textField.inputView=keypad;
[self.textField becomeFirstResponder];
}
I´m not in my Mac so I made probably some mistake here, but that´s the idea and That´s how I did it when I did my custom keypad as well and it´s universal (iPad and iPhone) for both landscape and portrait modes and so far it has given me no problems.

Resources