I'm currently trying to adjust the origin.y of a view based on when the keyboard shows/hides. I am able to adjust it accordingly however, on the iPhone X the contents of my adjusted view appears behind the status bar?
Below is the code for adjusting the view;
#objc func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if keyboardShown == false{
self.containerView.frame.origin.y -= keyboardSize.height
keyboardShown = true
print("Keyboard UP")
}
}
}
#objc func keyboardWillHide(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
if keyboardShown == true{
self.containerView.frame.origin.y += keyboardSize.height
keyboardShown = false
print("Keyboard DOWN")
}
}
They operated as intended minus the fact that the contents appears behind the statusbar (and is visible - overlapping with the time and signalbars etc.)
Below are screenshots to give a visual representation:
I am using Safe Area in the Interface builder, and have constrained my UIElements in accordance to the Safe Area. However, the container view (everything beside the custom navigation bar) seem to move beyond the Safe Area when adjusting the view.frame.origin.y.
Is there a way to take into consideration the Safe Area programatically when manipulating views?
Related
I have a rather complex situation. It has to do with moving a view above the keyboard. I have a chat view, much like the one Apple's Messages app. However, it's inside of a card that does not fill the entire screen. Above and below that card are a navigation bar and a tab bar.
Now, I want to move the editable UITextView (where the user composes their message) above the keyboard when they tap on it. I do have the height of the keyboard (using NotificationCenter Observer), but I need to subtract it from the height of the textview (inside of the card) from the height of the keyboard to calculate how much distance I should offset the textview by. A picture would probably help:
Here is my view stack:
View -> Card view -> ScrollView (that contains messages list tableview and chat tableview) -> TextBox View (that contains the UITextView, Send Button, etc.)
Is this doable? Is there a better way of doing this? I'm stuck and am pretty new at manipulating views in regards to the keyboard.
Here are scraps of code I have so far:
fileprivate var keyboardHeight = CGFloat()
#objc fileprivate func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let keyboardHeight = keyboardSize.height
self.keyboardHeight = keyboardHeight
}
}
fileprivate func moveTextView(textView: UITextView, moveDistance: Int, up: Bool) {
let moveDuration = 0.3
let movement = CGFloat(up ? moveDistance : -moveDistance)
UIView.animate(withDuration: moveDuration) {
self.snp.updateConstraints({ (make) in
make.bottom.equalToSuperview().offset(movement)
})
self.superview?.layoutIfNeeded()
}
}
extension TextBarView: UITextViewDelegate {
func textViewDidBeginEditing(_ textView: UITextView) {
moveTextView(textView: textView, moveDistance: -200, up: true)
}
func textViewDidEndEditing(_ textView: UITextView) {
moveTextView(textView: textView, moveDistance: -200, up: false)
}
func textViewShouldEndEditing(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
}
I am using SnapKit for autolayout.
(If you got through this entire post, thank you!)
I'm trying to create a page in an app that's your standard style messaging screen. I'm having trouble getting everything to position correctly when the keyboard slides into view. I'll post screenshots (sadly not inline), but here is my structure:
VIEWCONTROLLER
|-View
|-Scroll View
|-Content View
|-TextField
|-TableView (messages)
Everything is showing up as I would like it to when first loaded: If there aren't enough messages to fill the screen, the messages start at the top followed by a gap, and the text field is pinned to the bottom. Nothing scrolls. If there are a lot of messages, I am successfully scrolling the table to the last row and the textfield is pinned to the bottom of the screen still.
When the textfield is activated however, and there aren't a lot of messages, the gap between the table and the textfield remains and the messages are pushed out of view to the top.
I am trying to get the gap to shrink so the messages stay. This is standard in other messaging apps, but I cannot figure out how to do it
Initial view
Textfield activated, keyboard appears
Scrolling to display messages hides the textfield
UI Layout and constraints
Lastly, here is the code I have for keyboardWillShow. You'll notice some comments of things I have tried unsuccessfully.
func keyboardWillShow(notification:NSNotification) {
var userInfo = notification.userInfo!
let keyboardFrame = (userInfo[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
let contentInsets: UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardFrame!.height, 0.0)
self.scrollView.contentInset = contentInsets
self.scrollView.scrollIndicatorInsets = contentInsets
// scrollViewBottomConstraint.constant = keyboardFrame!.height - bottomLayoutGuide.length
// contentViewHeightConstraint.constant = -keyboardFrame!.height
// self.notificationReplyTable.frame.size.height -= keyboardFrame!.height
var aRect: CGRect = self.view.frame
aRect.size.height -= keyboardFrame!.height
if let activeField = self.activeField {
if(!aRect.contains(activeField.frame.origin)) {
self.scrollView.scrollRectToVisible(activeField.frame, animated: true)
}
}
}
I feel like the piece I'm missing is pretty small, but just don't know enough Swift 3 to nail this. Thank you for your help!
Edit: the problem is similar to this question with no accepted answer.
A way to this is to set up vertical autolayout constraints like this (but you will need a reference to the actual bottomMargin constraint to be able to modify it) :
"V:|[scrollView][textField]-(bottomMargin)-|"
The first time you arrive on the screen, bottomMargin is set to 0.
Then when keyboardWillShow is called, get the keyboard frame (cf How to get height of Keyboard?)
func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
let keyboardHeight = keyboardRectangle.height
}
}
And animate the constraint bottomMargin to get the height of the keyboard (the duration is 0.3 after some tests, but you can adjust it) :
bottomConstraint.constant = keyboardHeight
UIView.animate(withDuration: 0.3, delay: 0, options: nil, animations: {
self.view.layoutIfNeeded()
}
That means that every time the keyboard will appear, an animation will move up the text field, hence the scroll view height will be smaller and everything will fit in the screen.
!! Don't forget to test it on landscape mode if you support it, and on iPad too!!
Finally, handle the case when the keyboard will disappear in the keyboardWillHide and set bottomMargin back to 0 :
func keyboardWillHide(_ notification: Notification) {
bottomConstraint.constant = 0
UIView.animate(withDuration: 0.3, delay: 0, options: nil, animations: {
self.view.layoutIfNeeded()
}
}
My view has a TextView with 0 as the number of lines, populated with a long text that the user can edit.
When the keyboard shows, to resize the TextView so that it fits in the visible portion of the screen, the height constraint of an empty view at the bottom of the screen is equaled to the keyboard height. The bottom of the textView is constrained to the top of the empty view, so the text view gets resized.
When this happens, the textView automatically scrolls down in the text. I would like the text to avoid this scrolling, so that the first line always stays visible. I have tried a few ways, such as:
Disabling scrolling between keyboardWillShow and keyboardDidShow, not working
Scrolling back to zero, but we can see the text scrolling down and then scrolling back up (textView.scrollRangeToVisible(NSRange(location:0, length:0)))
Here's a drawing to make it much clearer (can't embed it yet, sorry):
UITextView Drawing
Relevant code:
func keyboardWillShow(_ notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue {
let duration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
emptyViewHeightConstraint.constant = keyboardSize.height
UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() }
}
}
func keyboardWillHide(_ notification: NSNotification) {
let duration: TimeInterval = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue
emptyViewHeightConstraint.constant = 0
UIView.animate(withDuration: duration) { self.view.layoutIfNeeded() }
}
PS: The TextView is actually inside a StackView, and this StackView has its bottom constrained to the top of the empty view.
I am attempting, when the keyboard appears, to shift the view up. This works on two views, but on the third the same code causing the view to seemingly move down a certain amount, then move back into the exact place it started, or so the animation seems. Debugging, I see nowhere else the self.view.frame is getting set but this method. In addition, the offsets look right, as if the view should move up like the other views have. See the keyboardWillShow method below.
func keyboardWillShow(notification: NSNotification){
if self.origFrame == nil{
self.origFrame = self.view.frame
}
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue(){
var testRect = self.view.frame
testRect.size.height -= keyboardSize.height
if !testRect.contains(loginBtn!.frame.origin){
let bottomSpace = self.view.frame.size.height - loginBtn.frame.origin.y - loginBtn.frame.size.height
let keyboardOverlap = keyboardSize.height - bottomSpace
let newY = self.origFrame!.origin.y - keyboardOverlap
self.view.frame.origin.y = newY
}
}
}
This is what I've done in the past
func textViewDidBeginEditing(textView: UITextView) {
let point:CGPoint = CGPoint(x: textView.frame.origin.x - 8, y: textView.frame.origin.y - 100)
scrollView.contentOffset = point
}
This moves the view up based on the position of textview the user tapped on. 8 and 100 pixels just happened to be good ranges for my specific purpose.
Alternatively, instead of moving the frames, you could programmatically adjust your constraints.
I have a chat view and I transition the textView up with the keyboard by changing the height of the view. However, when I change the keyboard type to emoji, and also back to regular keyboard, the UIKeyboardWillShowNotification fires again and move the view an additional step up (ie an additional height of the keyboard).
How can I keep track of this and make sure I only subtract the height of a keyboard if it is not already subtracted, or only subtract the additional height of the emoji keyboard ?
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.size.height = self.view.frame.size.height - keyboardSize.height
self.view.layoutIfNeeded()
}
}
I change keyboard to emoji
I change keyboard from emoji and back to normal
Instead of using self.view.frame.height, use screen height.
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.view.frame.size.height = UIScreen.mainScreen().bounds.size.height - keyboardSize.height
self.view.layoutIfNeeded()
}
}
An even more simpler way is to import this third party utility in your code
https://github.com/hackiftekhar/IQKeyboardManager
It automatically handles everything you want.