Black screen at bottom in landscape mode when dismiss keyboard IQKeyboardManager - ios

I am using the IQKeyboardManagerSwift latest version I am having issue black screen at bottom --> When I tap on text field in landscape mode of device keyboard comes up and view of controller and background image goes up black screen of window can be seen at bottom View does not come back to original position after keyboard dismiss.
I want to move container view of the text field up, not the view of the controller and background image, the container view is horizontally and vertically center in auto layout. I tried to add IQLayoutGuideConstraint to container view but no luck. it would be great if you can provide some information to achieve it. please seen storyboard screenshot below

try to remove your firstResponder from viewDidLoad, and put it in viewDidAppear instead, like this
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
yourTextField.becomeFirstResponder()
}

P.S. Do not set your textfield as firstResponder in your viewController.
For ex:- (in ViewDidLoad Method)
[self.yourTextField becomeFirstResponder];
If you have above set up. Please comment(Delete) this line and then try.
All the best.

Have you tried to update your pods?
pod IQKeyboardManagerSwift

workround, place in your VC viewillappear:
override func viewWillAppear(_ animated: Bool) {
if let window = (UIApplication.shared.delegate?.window)! as UIWindow? {
window.backgroundColor = UIColor.white
}
}

Related

Swift: Dismiss ViewController without keyboard slider

I present/dismiss ViewController with TextField like a fade. And keyboard do not need to slide on/out of the screen.
For presenting i did that by:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
textField.becomeFirstResponder()
How can i do that for dismiss?
It should not move somehow, i fade out VC and it should fade out with it as well. But insted of that it's moving down
You trying to achive an UI effect which is doesn't comply to Human Interface Guidelines. This usualy means that you you have to find out an trick or a hack to achive it.

Accessibility set focus to navigation bar title item

Overview:
I would like to set the accessibility focus to the navigation bar's title item.
By default the focus is set from top left, meaning the back button would be on focus.
I would like the title item to be in focus.
Attempts made so far:
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
navigationController?.navigationBar.items?.last)
Problem:
The above code makes no difference, the back button is still in focus.
Possible Cause:
Not able to get the item corresponding to the title to be able to set the focus.
Solution 1
I don't like it, but it was the minimum amount of hacking that does not rely on digging through hidden subviews (internal implementation of UINavigationBar view hierarchy).
First in viewWillAppear, I store a backup reference of the back button item,
and then remove the back button item (leftBarButtonItem):
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
backButtonBackup = self.navigationItem.leftBarButtonItem
self.navigationItem.leftBarButtonItem = nil
}
Then I restore the back item, but only after I dispatch the screen changed event in viewDidAppear() :
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
self?.navigationItem.leftBarButtonItem = self?.backButtonBackup
}
}
Solution 2:
Disable all accessibility on the nav bar and view controller up until viewDidAppear() is finished:
self.navigationController.navigationBar.accessibilityElementsHidden = true
self.view.accessibilityElementsHidden = true
, and then in viewDidAppear manually dispatching the layout element accessibility focused event to the label subview of UINavigationBar:
UIAccessibilityPostNotification( UIAccessibilityLayoutChangedNotification, self.navigationController.navigationBar.subviews[2].subviews[1])
// The label buried inside the nav bar. Not tested on all iOS versions.
// Alternately you can go digging for the label by checking class types.
// Then use DispatchAsync, to re-enable accessibility on the view and nav bar again...
I'm not a fan of this method either.
DispatchAsync delay in viewDidAppear seems to be needed in any case - and I think both solutions are still horrible.
I invoked UIAccessibilityScreenChangedNotification on navigation title from viewDidLoad and it worked
UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification,
self.navigationItem.title);
First, we'll need to create an useful extension:
extension UIViewController {
func setAccessibilityFocus(in view: UIView) {
UIAccessibility.post(notification: .screenChanged, argument: view)
}
}
Then, we'll be able to set our focus in the navigation bar title like this:
setAccessibilityFocus(in: self.navigationController!.navigationBar.subviews[2].subviews[1])

How to fix why status bar space behind ScrollView in Xcode?

I added a scroll view to my view which is hover the status bar (I hid it). The scroll view is working fine, but when I'm scrolling to the top, I have a white space which disappears when I tap on my screen, and appears again when I scroll down then top.
I noticed that the scroll bar is not going to the top of my view, but stopped at the status bar.
Here are screenshots which show you what I mean.
Here I'm at the top of my view but the scroll bar isn't:
Here is the same view with the white status bar which appears when I scroll top again:
It disappear when I tap on my screen or scroll down.
Here are my constraints:
I think it's a problem of Layout Margin or something like that, but I don't what I should change?
I hide the status bar like that in my view controller:
override func viewWillAppear(_ animated: Bool) {
UIApplication.shared.keyWindow?.windowLevel = UIWindowLevelStatusBar
super.viewWillAppear(animated)
}
EDIT: Even if I comment the line which hides the status bar, I still have the same problem with my scroll view. So the problem doesn't come from how I hide it.
As Sam said, I changed the content insets to "Never" on the scroll view and it works.
While unrelated to your question, I have to react to the way you hide the status bar - the proper way is to override prefersStatusBarHidden in your view controller and call self.setNeedsStatusBarAppearanceUpdate() in your viewWillAppear:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.setNeedsStatusBarAppearanceUpdate()
}
override var prefersStatusBarHidden: Bool {
return true
}
UPDATE
Since your view controller is inside of a UINavigationViewController, you need to override childViewControllerForStatusBarHidden in UINavigationViewController to use visibleViewController as the controller to determine status bar hidden (I added override to childViewControllerForStatusBarStyle for the consistence):
extension UINavigationController {
open override var childViewControllerForStatusBarStyle: UIViewController? {
return visibleViewController
}
open override var childViewControllerForStatusBarHidden: UIViewController? {
return visibleViewController
}
}

Keyboard does not disappear after viewDidDisappear

iOS 11.2, Xcode 9.2
I've tried all the ways to dismiss keyboard on viewDidDisappear of the old UIViewController AFTER a new UIViewController is pushed into UINavigationController stack. But with no luck.
If I dismiss it on viewWillDisappear - it will be dismissed but with animation DURING push animation. It is not the desired behaviour. I want the old UIViewController's keyboard to be dismissed only when the controller is no longer visible.
The behavior should be like in Telegram app:
In any dialog with visible keyboard press on opponents avatar and you'll be pushed to opponents account information. Then, if you press back button, you'll be redirected back to a dialog. But the keyboard will be already dismissed.
Any help is appreciated!
P.S. The question might look like a duplicate, but I have failed to make it work with the solutions I found.
Edit 1.
I have created a small TEST PROJECT which represents a failure to achieve the desired behavior.
In order to reproduce the undesired behavior:
Launch the app.
Tap on UITextField or UITextView and wait for the keyboard to appear.
Tap on "Next" button and wait for a new controller to be pushed.
Tap on "Back" button and wait for a new controller to be popped.
As a result - the initial view controller will have the active keyboard after the push/pop actions. I need the keyboard to be hidden after the push/pop actions. Also, the keyboard should not be dismissed before the initial view controller becomes invisible, it should be dismissed after viewDidDisappear action.
There are cases where no text field is the first responder but the keyboard is on screen. In these cases, the above methods fail to dismiss the keyboard.
Use the property: isEditable of your textView. Here is a tested code:
override func viewWillAppear(_ animated: Bool) {
self.viewTextView.isEditable = false
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.viewTextView.isEditable = true
}
Results:
Comments from #iWheelBuy:
Sometimes, text views will have inputAccessoryView. The way you do it
will make the keyboard disappear, but the inputAccessoryView will
remain... That is why you should also make inputAccessoryView = nil
or inputAccessoryView = UIView() when setting isEditable = false
The problem happens because responders are managed (restored, saved) by UIKit between viewWillAppear and viewDidAppear, just before view has appeared, and between viewWillDisapear: and viewDidDisapear:, just before view has disappeared. That is why any change made to responders is visible during animation.
Instead of removing responders, to get the effect you want, you can prevent views from becoming responders before view appears again.
The simplest way to do this for UITextField and UITextView is to temporary disable interaction just before view will appear, and then restore it after the view did reappeared.
override func viewWillAppear(_ animated: Bool) {
self.viewTextField.isUserInteractionEnabled = false
self.viewTextView.isUserInteractionEnabled = false
super.viewWillAppear(animated)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.viewTextField.isUserInteractionEnabled = true
self.viewTextView.isUserInteractionEnabled = true
}
This will give you the same effect Telegram has.

Toolbar incorrectly positioned during push animation with hidesBottomBarWhenPushed

I have an App using a Tabbar for basic Navigation. From one of the screens of the Tabbar I want to enter another one that shows a toolbar instead of the Tabbar and a back navigation item on the top.
What is the best way to do this? If I use "Hide Bottom Bar on Push" (aka hidesBottomBarWhenPushed) and add a Toolbar to the screen I can see an animation removing the Tabbar before the Toolbar is placed at the bottom of the screen.
Solution for UITableViewController with toolbar (requires code)
Using code from this answer, I was able to achieve the same effect, but with the toolbar at the bottom of a table view.
Add this to your table view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self.navigationController setToolbarHidden:NO animated:YES];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[self.navigationController setToolbarHidden:YES animated:YES];
}
Important note: placing these calls in viewWillAppear and viewWillDisappear instead of viewDidLoad makes this easier to handle, as it will work reliably even for multiple pushes and pops of the same view controller, and you won't have to clean up after it in the previous view controller.
And configure it like this in the storyboard:
Also, enable Hides bottom bar when pushed in the storyboard, or in your code, for the view controller being pushed.
Then you can add toolbar buttons to the toolbar in the storyboard.
Build and run, and you get this effect:
Here's a complete sample project demonstrating this.
Problem Example
Here is my solution,
In the first view controller that has the tabbar do this
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "someSegue" {
if let secondVC = segue.destinationViewController as? InfoTableViewController {
secondVC.hidesBottomBarWhenPushed = true
}
}
}
I also needed this, as my toolbar would re appear in the first VC.
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
navigationController?.toolbarHidden = true
}
To stop the fade up animation of the toolbar, so its just there i used this in the second VC
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.toolbarHidden = false
}
Pure storyboard solution
If you're referring to the issue of the toolbar appearing above the tab bar during the push transition animation, I was able to fix this by adjusting the auto layout constraints on the toolbar in the storyboard (add it manually to your view controller; see my other answer if you're using a UITableViewController or UICollectionViewController and can't do this):
Add a constraint to set the distance to the bottom layout guide to zero:
Double click that constraint to edit it, and set the first item to Bottom (it will be Top by default).
All done! This will result in an effect like this:
Here's my sample project that demonstrates this working as expected. Note that I didn't change any of the code, everything is in the storyboard.
As of Xcode 7, the pure Storyboard solution doesn't work anymore because Xcode wouldn't let you assign the Bottom attribute to the Bottom Layout Guide anymore.
For my project, I used the following setup:
A UITabBarController as initial view controller, going into a
UINavigationController, with root vc set to...
UIRegularViewController, which should behave normally, but spawn a...
UISpecialViewController, which should hide the tab bar and instead display a toolbar. Also, it should hide the status bar, the navigation bar and the tool bar on tap.
Here is what I did to achieve this:
In the storyboard
UITabBarController: set Tab Bar Translucency to NO
UISpecialViewController: Set Simulated Metrics like so
Status Bar: None
Top Bar: Opaque Nav Bar
Bottom Bar: Opaque Toolbar
Set Extended Edges like this:
Under Top Bars: NO
Under Bottom Bars: YES
Under Opaque Bars: YES
Do not drag a UIToolBar into UISpecialViewController !
In the Implementations
// in UISpecialViewController.m
- (void)viewWillAppear:(BOOL)animated {
self.navigationController.toolbarHidden = NO;
self.navigationController.hidesBarsOnTap = YES;
}
- (void)viewWillDisappear:(BOOL)animated {
self.navigationController.toolbarHidden = YES;
self.navigationController.hidesBarsOnTap = NO;
}
- (BOOL)prefersStatusBarHidden {
return self.navigationController.navigationBarHidden;
}
Here is the Demo Code.
This is the result:
In fact, UIKit has already configured how Toolbar and Tabbar change in the page switching animation.
I also have this situation with you today, and the final solution surprised me.
For example, page A to page B, page A displays Tabbar, not Toolbar, page B does not display Toolbar, and does not display Tabbar.
At this time, B needs to set hidesBottomBarWhenPushed to true, which is necessary.
Then, in the declaration cycle of the two ViewControllers, in the viewWillDisappear of A and the viewWillAppear of B, if you set the navigation controller setToolbarHidden, this animation problem will occur.
If you set it in viewDidDisappear of A and viewDidAppear of B, the problem is solved. Although the toolbar will have a delayed animation, it is always better than the wrong animation.
Finally add:
The order of A and B life cycle function calls is:
A - viewWillDisappear
B - viewWillAppear
A - viewDidDisappear
B - viewDidAppear
These four methods are interleaved.
Using Xcode 12.4 iOS 14.4
for those who struggle with this issue and try solutions above with no luck.
let's say A is with tabBar only, B is only showing toolbar
remember to set hidesBottomBarWhenPushed = true in B's init
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
hidesBottomBarWhenPushed = true
}
implement these below in B. (no need to do anything in A)
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.setToolbarHidden(true, animated: false)
}
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
navigationController?.setToolbarHidden(false, animated: false)
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
navigationController?.setToolbarHidden(true, animated: true)
}
that's it!!
p.s. if you want to remove the toolbar animation from bottom up then add this
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
navigationController?.toolbar.layer.removeAnimation(forKey: "position")
}

Resources