Programmatically scroll ScrollView with StackView inside of it - ios

I have a ScrollView with a StackView inside of it. When the keyboard appears I am changing the bottomConstraint .
1. view withouth keyboard
2. how it looks if keyboard shows
3. How it should look like
The problem is that I would like to scroll the ScrollView a bit up but I can not make it work.
scrollView.setContentOffset(CGPoint(x: x, y: y), animated: true) is NOT working. Ive tried it as you can see in the code but it has no effect:
Keyboard Observer methods
var keyboardHeight: CGFloat?
//MARK: keyboardObserver
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
self.keyboardHeight = keyboardRectangle.height
if self.passwordWiederholenTextField.isEditing {
scrollBottomViewConstraint.constant = -(self.keyboardHeight!)
self.theScrollView.setContentOffset(CGPoint(x: 0, y: 20), animated: true)
self.view.layoutIfNeeded()
}
}
}
#objc func keyboardWillHide(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
self.keyboardHeight = keyboardRectangle.height
if self.passwordWiederholenTextField.isEditing {
scrollBottomViewConstraint.constant = 0
self.view.layoutIfNeeded()
}
}
}
Constraints:
theScrollView.topAnchor.constraint(equalTo: theLabel.bottomAnchor, constant: 20).isActive = true
theScrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 30).isActive = true
theScrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -30).isActive = true
scrollBottomViewConstraint = theScrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
scrollBottomViewConstraint.isActive = true
theStackView.topAnchor.constraint(equalTo: theScrollView.topAnchor).isActive = true
theStackView.leadingAnchor.constraint(equalTo: theScrollView.leadingAnchor).isActive = true
theStackView.trailingAnchor.constraint(equalTo: theScrollView.trailingAnchor).isActive = true
theStackView.widthAnchor.constraint(equalTo: theScrollView.widthAnchor).isActive = true
stackViewBottomConstraint = theStackView.bottomAnchor.constraint(equalTo: theScrollView.bottomAnchor)
stackViewBottomConstraint.isActive = true
I couldn't find anything on this so if anyone has any idea why it is not working I am very grateful!

With #Arun's tip I managed to get it done. This is my final code and it works perfectly:
var keyboardHeight: CGFloat?
//MARK: keyboardObserver
#objc func keyboardWillShow(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
self.keyboardHeight = keyboardRectangle.height
let activeField: UITextField? = [passwordTextField, passwordWiederholenTextField].first { $0.isFirstResponder }
switch activeField {
case passwordTextField:
let insets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight! + 130, right: 0)
theScrollView.contentInset = insets
theScrollView.scrollIndicatorInsets = insets
self.view.layoutIfNeeded()
case passwordWiederholenTextField:
let insets = UIEdgeInsets(top: 0, left: 0, bottom: keyboardHeight! + 30, right: 0)
theScrollView.contentInset = insets
theScrollView.scrollIndicatorInsets = insets
self.view.layoutIfNeeded()
default:
break
}
}
}
#objc func keyboardWillHide(_ notification: Notification) {
if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue {
let keyboardRectangle = keyboardFrame.cgRectValue
self.keyboardHeight = keyboardRectangle.height
theScrollView.contentInset = UIEdgeInsets.zero
theScrollView.scrollIndicatorInsets = UIEdgeInsets.zero
}
}

Related

How I get the keyboard frame before the keyboard appears

I have a button where you get a textfield with a keyboard. At the moment, when I get this textfield, the textfield is in the left corner and the textfield appears. How can I handle this, that the textfield is above my keyboard?
The problem is, that I get the keyboard height after the code in editTextButtonTapped is done. How can I get the height of the keyboard before editTextButtonTapped is called?
// This are my global Variables to create the textfield
var myTextField: UITextField = UITextField(frame: CGRect(x: 0,y: 0, width: 0, height:0))
var firstkeyboardHeight = CGFloat()
NotificationCenter.default.addObserver(self, selector: #selector(keyboardDidShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
#objc func keyboardWillShow(_ notification: NSNotification) -> Void {
if let keyboardRect = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as AnyObject).cgRectValue {
let keyboardHeight = keyboardRect.height
firstkeyboardHeight = keyboardHeight
}
}
#IBAction func editTextButtonTapped(_ sender: UIButton) {
myTextField.becomeFirstResponder()
self.myTextField = UITextField(frame: CGRect(x: 0,y: self.firstkeyboardHeight, width: self.view.frame.width, height: 50.0))
self.myTextField.backgroundColor = .green
self.Gestures(addGesture: self.myTextField)
self.view.addSubview(self.myTextField)
}
There is only this set of notifications:
[ UIResponder.keyboardWillShowNotification, UIResponder.keyboardWillHideNotification, UIResponder.keyboardDidShowNotification, UIResponder.keyboardDidHideNotification]
Only inside can obtain the frame values:
NotificationCenter.default.addObserver(forName: UIResponder.keyboardWillHideNotification,
object: nil,
queue: .main) { [weak self] notification in
guard let userInfo = notification.userInfo else {
return
}
let initialValue = userInfo[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue,
let finalValue = userInfo[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue,
let durationValue = userInfo[UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber,
let curveValue = userInfo[UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
}
So, I don't think that there are any possible allowed ways.
You have several ways to do that. In either case I'd avoid recreating the textfield every time.
let myTextField: UITextField = {
let textfield = UITextField()
textfield.translatesAutoresizingMaskIntoConstraints = false
textfield.backgroundColor = .green
textfield.isHidden = true
return textfield
}()
var bottomConstraint: NSLayoutConstraint?
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(myTextField)
let bottomConstraint = myTextField.bottomAnchor.constraint(equalTo: view.bottomAnchor)
NSLayoutConstraint.activate([
myTextField.leadingAnchor.constraint(equalTo: view.leadingAnchor),
myTextField.trailingAnchor.constraint(equalTo: view.trailingAnchor),
myTextField.heightAnchor.constraint(equalToConstant: 50),
bottomConstraint
])
self.bottomConstraint = bottomConstraint
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(_:)), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(_ notification: NSNotification) -> Void {
guard let keyboardFrame = (notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue else { return }
bottomConstraint?.constant = -keyboardFrame.height
}
#IBAction func editTextButtonTapped(_ sender: UIButton) {
myTextField.becomeFirstResponder()
myTextField.isHidden = false
}

Hamburger Menu UIPanGestureRecogniser

I am trying to make a hamburger menu for my app programmatically and using no plugins. I am starting with the basics, so just trying to get 2 UIViews working together when the user swipes right. The 2 views are the main view and the hamburger menu.
So far the UIViews load in the correct place and when the user swipes right the sidebar slides along. However, when the user lets go the UIView also slides up to the center of the UIView is pinned to the top of the screen. See images below:
Once it gets lost up there I cannot pull it back down. I can still swipe left and right but the center stays constrained to the top of the screen.
I've looked through my code and cannot see what I am doing wrong here?
Here is my code:
class gestureSwipe: UIViewController, UIGestureRecognizerDelegate {
let screenHeight = UIScreen.main.bounds.height
let screenWidth = UIScreen.main.bounds.width
var trayOriginalCenter: CGPoint!
var sideBarSwipeLeftOffset: CGFloat!
var siderBarSwipeRight: CGPoint!
var sideBarSwipeLeft: CGPoint!
let sideBarUIView: UIView! = {
let sideBarUIView = UIView()
sideBarUIView.backgroundColor = UIColor(red:1.0, green:0.0, blue:0.0, alpha:1.0)
sideBarUIView.translatesAutoresizingMaskIntoConstraints = false
sideBarUIView.isUserInteractionEnabled = true
return sideBarUIView
}()
let mainView: UIView = {
let mainView = UIView()
mainView.backgroundColor = UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0)
mainView.translatesAutoresizingMaskIntoConstraints = false
mainView.isUserInteractionEnabled = true
return mainView
}()
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(barButtonSystemItem: .edit, target: self, action: #selector(HandleSettings))
navigationItem.leftBarButtonItem = settingsButton
view.backgroundColor = UIColor.white
view.addSubview(mainView)
view.addSubview(sideBarUIView)
let SideBarPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
let MainViewPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
mainView.addGestureRecognizer(MainViewPanGestureRecognizer)
sideBarUIView.addGestureRecognizer(SideBarPanGestureRecognizer)
sideBarSwipeLeftOffset = 80
siderBarSwipeRight = sideBarUIView.center
sideBarSwipeLeft = CGPoint(x: sideBarUIView.center.x + sideBarSwipeLeftOffset, y: sideBarUIView.center.y)
setupLayout()
}
#IBAction func didPan(sender: UIPanGestureRecognizer) {
let velocity = sender.velocity(in: view)
let translation = sender.translation(in: view)
if sender.state == .began {
trayOriginalCenter = sideBarUIView.center
} else if sender.state == .changed {
print("Gesture began")
sideBarUIView.center = CGPoint(x: trayOriginalCenter.x + translation.x, y: trayOriginalCenter.y)
} else if sender.state == .ended {
print("Gesture ended")
if velocity.x > 0 {
UIView.animate(withDuration: 0.3) {
self.sideBarUIView.center = self.sideBarSwipeLeft
}
} else {
UIView.animate(withDuration: 0.3) {
self.sideBarUIView.center = self.siderBarSwipeRight
}
}
}
}
#IBAction func HandleSettings(sender : UIButton) {
print ("Show settings")
}
private func setupLayout(){
// CONSTRAINTS
mainView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
mainView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
sideBarUIView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
sideBarUIView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
sideBarUIView.rightAnchor.constraint(equalTo: view.leftAnchor, constant: 0).isActive = true
}
}
So I figured out the problem. In the sender.state == .ended, I was using self.sideBarUIView.center = self.sideBarSwipeLeft which kept pushing the view up to the center of the screen.
Edit:
I decided to make the hamburger menu from scratch. Taking some ideas from this example: https://github.com/iosapptemplates/drawer-menu-swift
Then I finally went and added some nice touches like if the menu isn't swiped over enough it was hidden away again.
Here is my final code if anyone needs it.
class hamburgerMenu: UIViewController, UIGestureRecognizerDelegate {
let screenHeight = UIScreen.main.bounds.height
let screenWidth = UIScreen.main.bounds.width
var sideBarOriginalCenter: CGPoint!
var mainView: UIView! = {
let mainView = UIView()
mainView.backgroundColor = UIColor(red:0.0, green:1.0, blue:0.0, alpha:1.0)
mainView.translatesAutoresizingMaskIntoConstraints = false
return mainView
}()
var overlayView: UIView! = {
let viewBlack = UIView()
viewBlack.backgroundColor = UIColor(red:0.0, green:0.0, blue:0.0, alpha:1.0)
viewBlack.translatesAutoresizingMaskIntoConstraints = false
return viewBlack
}()
var sideBarUIView: UIView! = {
let sideBarUIView = UIView()
sideBarUIView.backgroundColor = UIColor(red:1.0, green:0.0, blue:0.0, alpha:1.0)
sideBarUIView.translatesAutoresizingMaskIntoConstraints = false
return sideBarUIView
}()
override func viewDidLoad() {
super.viewDidLoad()
let settingsButton = UIBarButtonItem(barButtonSystemItem: .search, target: self, action: #selector(HandleSettings))
navigationItem.leftBarButtonItem = settingsButton
view.backgroundColor = UIColor.white
view.addSubview(mainView)
view.addSubview(overlayView)
view.addSubview(sideBarUIView)
overlayView.alpha = 0
let swipeLeftGesture = UIPanGestureRecognizer(target: self, action: #selector(didPan(sender:)))
mainView.addGestureRecognizer(swipeLeftGesture)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTapOverlay))
mainView.addGestureRecognizer(tapGesture)
setupLayout()
}
#IBAction func didPan(sender: UIPanGestureRecognizer) {
let translation = sender.translation(in: view)
if sender.state == .began {
sideBarOriginalCenter = sideBarUIView.center
} else if sender.state == .changed {
sideBarUIView.center = CGPoint(x: sideBarOriginalCenter.x + translation.x, y: sideBarUIView.center.y)
} else if sender.state == .ended {
let negHalfScreenWidth = ((self.screenWidth/2) * -1) / 2 // This should make -187.5 on iphoneX
if sideBarUIView.center.x > negHalfScreenWidth {
UIView.animate(withDuration: 0.3) {
if self.sideBarUIView.center.x > negHalfScreenWidth {
let leftSideOfScreen = self.screenWidth - self.screenWidth
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
}
}
} else {
UIView.animate(withDuration: 0.3) {
let leftSideOfScreen = (self.screenWidth / 2) * -1
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
}
}
}
}
#IBAction fileprivate func didTapOverlay() {
UIView.animate(withDuration: 0.3, animations: {
let leftSideOfScreen = (self.screenWidth / 2) * -1
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
}) { (success) in
}
}
#IBAction func HandleSettings(sender : UIButton) {
if (sideBarUIView.center.x == 0) {
UIView.animate(withDuration: 0.3, animations: {
let leftSideOfScreen = (self.screenWidth / 2) * -1
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
})
} else if (sideBarUIView.center.x < -0.1) {
UIView.animate(withDuration: 0.3, animations: {
let leftSideOfScreen = self.screenWidth - self.screenWidth
self.sideBarUIView.center = CGPoint(x: leftSideOfScreen ,y: self.sideBarUIView.center.y)
})
}
}
private func setupLayout(){
mainView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
mainView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
sideBarUIView.rightAnchor.constraint(equalTo: view.leftAnchor, constant: 20).isActive = true
sideBarUIView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
sideBarUIView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
sideBarUIView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
overlayView.heightAnchor.constraint(equalToConstant: screenHeight).isActive = true
overlayView.widthAnchor.constraint(equalToConstant: screenWidth).isActive = true
}
}

TextField bottom with iPhone X in Swift

I'm new in Swift, and I have an issue with the iPhone X.
I followed this tutorial: https://www.youtube.com/watch?v=FDay6ocBlnE&index=8&list=PL0dzCUj1L5JEfHqwjBV0XFb9qx9cGXwkq in order to create a Chat App.
My problem is that the textField is fixed to the bottom, and that is not good for the iPhone X.
I really don't know how I can change this, given that I'm more familiar with storyboard and here, the collectionViewController is entirely programmatically. I searched a lot of other tutorials but I found nothing to help.
This is my code:
The bottom view (with the textfield):
class ChatInputContainerView: UIView, UITextFieldDelegate {
// ...
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
// ...
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
The CollectionViewController:
class ChatLogController: UICollectionViewController, UICollectionViewDelegateFlowLayout {
// ...
lazy var inputContainerView: ChatInputContainerView = {
// I can't change the y value (it changes nothing)
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 54))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
}
Update
Here's the entire code:
import UIKit
import UserNotifications
class ChatLogController: UICollectionViewController, UITextFieldDelegate, UICollectionViewDelegateFlowLayout, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
var user: Userm? {
didSet {
navigationItem.title = user?.username
loadMessages()
}
}
var messages = [Message]()
func loadMessages() {
guard let toId = user?.id else {
return
}
Api.Message.observeUserDiscussion(toId: toId) { (message) in
self.messages.append(message)
DispatchQueue.main.async(execute: {
self.collectionView?.reloadData()
//scroll to the last index
let indexPath = IndexPath(item: self.messages.count - 1, section: 0)
self.collectionView?.scrollToItem(at: indexPath, at: .bottom, animated: true)
})
}
}
let cellId = "cellId"
override func viewDidLoad() {
super.viewDidLoad()
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
print("Notification settings: \(settings)")
guard settings.authorizationStatus == .authorized else { return }
//Not authorised
UIApplication.shared.registerForRemoteNotifications()
}
navigationItem.backBarButtonItem = UIBarButtonItem(title: " ", style: .plain, target: nil, action: nil)
collectionView?.contentInset = UIEdgeInsets(top: 8, left: 0, bottom: 20, right: 0)
// collectionView?.scrollIndicatorInsets = UIEdgeInsets(top: 0, left: 0, bottom: 50, right: 0)
collectionView?.alwaysBounceVertical = true
collectionView?.backgroundColor = UIColor.white
collectionView?.register(ChatMessageCell.self, forCellWithReuseIdentifier: cellId)
collectionView?.keyboardDismissMode = .interactive
arrowBackButton(greyBack)
let image = UIImage(named: "iconProfilCog")
navigationItem.rightBarButtonItem = UIBarButtonItem(image: image, style: .plain, target: self, action: #selector(handleParamsMessage))
navigationItem.rightBarButtonItem?.tintColor = UIColor(red: 203/255, green: 203/255, blue: 203/255, alpha: 1)
setupKeyboardObservers()
emptyTextField()
}
func emptyTextField() {
self.inputContainerView.inputTextField.text = ""
self.inputContainerView.sendButton.isEnabled = false
self.inputContainerView.sendButton.alpha = 0.8
}
override func viewDidLayoutSubviews() {
inputContainerView.inputTextField.roundCorners([.topLeft,.bottomLeft], radius: 10)
inputContainerView.backgroundSendButtonView.roundCorners([.topRight,.bottomRight], radius: 22)
}
lazy var inputContainerView: ChatInputContainerView = {
let chatInputContainerView = ChatInputContainerView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 54))
chatInputContainerView.chatLogController = self
return chatInputContainerView
}()
func handleParamsMessage() {
print("params")
let storyboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let detailMessage = storyboard.instantiateViewController(withIdentifier: "MessageDetailTableViewController") as! MessageDetailTableViewController
if let user = user {
detailMessage.userId = user.id!
self.navigationController?.pushViewController(detailMessage, animated: true)
}
}
func handleUploadTap() {
let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = true
imagePickerController.delegate = self
//imagePickerController.mediaTypes = [kUTTypeImage as String, kUTTypeMovie as String]
present(imagePickerController, animated: true, completion: nil)
}
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// if let videoUrl = info[UIImagePickerControllerMediaURL] as? URL {
// //we selected a video
// handleVideoSelectedForUrl(videoUrl)
// } else {
// //we selected an image
handleImageSelectedForInfo(info as [String : AnyObject])
// }
dismiss(animated: true, completion: nil)
}
fileprivate func handleImageSelectedForInfo(_ info: [String: AnyObject]) {
var selectedImageFromPicker: UIImage?
if let editedImage = info["UIImagePickerControllerEditedImage"] as? UIImage {
selectedImageFromPicker = editedImage
} else if let originalImage = info["UIImagePickerControllerOriginalImage"] as? UIImage {
selectedImageFromPicker = originalImage
}
if let selectedImage = selectedImageFromPicker {
HelperService.uploadMessagePictureToDatabase(selectedImage, completion: { (imageUrl) in
self.sendMessageWithImageUrl(imageUrl, image: selectedImage)
})
}
}
override var inputAccessoryView: UIView? {
get {
return inputContainerView
}
}
override var canBecomeFirstResponder : Bool {
return true
}
func setupKeyboardObservers() {
NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardDidShow), name: NSNotification.Name.UIKeyboardDidShow, object: nil)
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
NotificationCenter.default.removeObserver(self)
}
func handleKeyboardDidShow() {
if messages.count > 0 {
let indexPath = IndexPath(item: messages.count - 1, section: 0)
collectionView?.scrollToItem(at: indexPath, at: .top, animated: true)
}
}
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return messages.count
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellId, for: indexPath) as! ChatMessageCell
cell.chatLogController = self
let message = messages[indexPath.item]
cell.textView.text = message.text
setupCell(cell, message: message)
//lets modify the bubbleView's width somehow???
// cell.bubbleWidthAnchor?.constant = estimateFrameForText(message.text!).width + 25
if let text = message.text {
//a text message
cell.bubbleWidthAnchor?.constant = estimateFrameForText(text).width + 25
cell.textView.isHidden = false
} else if message.imageUrl != nil {
//fall in here if its an image message
cell.bubbleWidthAnchor?.constant = 200
cell.textView.isHidden = true
}
return cell
}
fileprivate func setupCell(_ cell: ChatMessageCell, message: Message) {
if let profileImageUrl = self.user?.profileImageUrl {
let photoUrl = URL(string: profileImageUrl)
cell.profileImageView.sd_setImage(with: photoUrl)
}
if message.fromId == Api.User.CURRENT_USER?.uid {
//outgoing blue
cell.bubbleView.backgroundColor = ChatMessageCell.blueColor
cell.textView.textColor = UIColor.white
cell.profileImageView.isHidden = true
cell.tailImageView.isHidden = true
cell.bubbleViewRightAnchor?.isActive = true
cell.bubbleViewLeftAnchor?.isActive = false
} else {
//incoming gray
cell.bubbleView.backgroundColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
cell.textView.textColor = UIColor(red: 70/255, green: 70/255, blue: 70/255, alpha: 1)
cell.profileImageView.isHidden = false
cell.tailImageView.isHidden = false
cell.bubbleViewRightAnchor?.isActive = false
cell.bubbleViewLeftAnchor?.isActive = true
}
if let messageImageUrl = message.imageUrl {
let photoUrl = URL(string: messageImageUrl)
cell.messageImageView.sd_setImage(with: photoUrl)
cell.messageImageView.isHidden = false
// cell.bubbleView.backgroundColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
} else {
cell.messageImageView.isHidden = true
}
}
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
collectionView?.collectionViewLayout.invalidateLayout()
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
var height: CGFloat = 80
let message = messages[indexPath.item]
if let text = message.text {
height = estimateFrameForText(text).height + 18
} else if let imageWidth = message.imageWidth?.floatValue, let imageHeight = message.imageHeight?.floatValue {
// h1 / w1 = h2 / w2
// solve for h1
// h1 = h2 / w2 * w1
height = CGFloat(imageHeight / imageWidth * 200)
}
let width = UIScreen.main.bounds.width
return CGSize(width: width, height: height)
}
fileprivate func estimateFrameForText(_ text: String) -> CGRect {
let size = CGSize(width: 200, height: 1000)
let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
return NSString(string: text).boundingRect(with: size, options: options, attributes: [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15, weight: .medium)], context: nil)
}
var containerViewBottomAnchor: NSLayoutConstraint?
func handleSend() {
self.inputContainerView.sendButton.isEnabled = false
let properties = ["text": inputContainerView.inputTextField.text!]
sendMessageWithPropertiesFIR(properties as [String : AnyObject])
}
fileprivate func sendMessageWithImageUrl(_ imageUrl: String, image: UIImage) {
let properties: [String: AnyObject] = ["imageUrl": imageUrl as AnyObject, "imageWidth": image.size.width as AnyObject, "imageHeight": image.size.height as AnyObject]
sendMessageWithPropertiesFIR(properties)
}
func sendMessageWithPropertiesFIR(_ properties: [String: AnyObject]) {
print(properties["text"])
var messageText = ""
if properties["text"] != nil {
messageText = properties["text"] as! String
} else {
messageText = "A envoyé une photo"
}
Api.Message.sendMessageWithProperties(toId: user!.id!, properties: properties) {
Api.Message.isUserMuted(userId: self.user!.id!, completion: { (isMuted) in
if !isMuted {
Api.UserToken.observeUserToken(withUser: self.user!.id!, completion: { (token) in
if let token = token {
Api.User.observeCurrentUser(completion: { (user) in
Api.Notification.sendNotifPush(token: token, message: "\(user.username!): \(messageText)")
})
}
})
}
})
self.emptyTextField()
}
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
handleSend()
return true
}
var startingFrame: CGRect?
var blackBackgroundView: UIView?
var startingImageView: UIImageView?
//my custom zooming logic
func performZoomInForStartingImageView(_ startingImageView: UIImageView) {
self.startingImageView = startingImageView
self.startingImageView?.isHidden = true
self.inputContainerView.inputTextField.resignFirstResponder()
startingFrame = startingImageView.superview?.convert(startingImageView.frame, to: nil)
let zoomingImageView = UIImageView(frame: startingFrame!)
zoomingImageView.backgroundColor = UIColor.red
zoomingImageView.image = startingImageView.image
zoomingImageView.isUserInteractionEnabled = true
zoomingImageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(handleZoomOut)))
if let keyWindow = UIApplication.shared.keyWindow {
blackBackgroundView = UIView(frame: keyWindow.frame)
blackBackgroundView?.backgroundColor = UIColor.black
blackBackgroundView?.alpha = 0
keyWindow.addSubview(blackBackgroundView!)
keyWindow.addSubview(zoomingImageView)
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
// self.inputContainerView.inputTextField.resignFirstResponder()
self.blackBackgroundView?.alpha = 1
self.inputContainerView.alpha = 0
// math?
// h2 / w1 = h1 / w1
// h2 = h1 / w1 * w1
let height = self.startingFrame!.height / self.startingFrame!.width * keyWindow.frame.width
zoomingImageView.frame = CGRect(x: 0, y: 0, width: keyWindow.frame.width, height: height)
zoomingImageView.center = keyWindow.center
}, completion: { (completed) in
// do nothing
})
}
}
func handleZoomOut(_ tapGesture: UITapGestureRecognizer) {
if let zoomOutImageView = tapGesture.view {
//need to animate back out to controller
zoomOutImageView.layer.cornerRadius = 8
zoomOutImageView.clipsToBounds = true
UIView.animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut, animations: {
self.startingFrame = self.startingImageView?.superview?.convert((self.startingImageView?.frame)!, to: nil)
zoomOutImageView.frame = self.startingFrame!
self.blackBackgroundView?.alpha = 0
self.inputContainerView.alpha = 1
}, completion: { (completed) in
zoomOutImageView.removeFromSuperview()
self.startingImageView?.isHidden = false
})
}
}
}
The View:
import UIKit
class ChatInputContainerView: UIView, UITextFieldDelegate {
weak var chatLogController: ChatLogController? {
didSet {
sendButton.addTarget(chatLogController, action: #selector(ChatLogController.handleSend), for: .touchUpInside)
uploadImageView.addGestureRecognizer(UITapGestureRecognizer(target: chatLogController, action: #selector(ChatLogController.handleUploadTap)))
inputTextField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
}
}
let inputColor = UIColor(red: 243/255, green: 243/255, blue: 243/255, alpha: 1)
lazy var inputTextField: UITextField = {
let textField = UITextField()
textField.placeholder = "Entrer un message..."
textField.translatesAutoresizingMaskIntoConstraints = false
textField.delegate = self
textField.backgroundColor = inputColor
// textField.roundCorners([.topLeft,.bottomLeft], radius: 10)
textField.clipsToBounds = true
return textField
}()
let uploadImageView: UIImageView = {
let uploadImageView = UIImageView()
uploadImageView.isUserInteractionEnabled = true
uploadImageView.image = UIImage(named: "pinImage")
uploadImageView.translatesAutoresizingMaskIntoConstraints = false
return uploadImageView
}()
lazy var backgroundSendButtonView: UIView = {
let backgroundSendButtonView = UIView()
backgroundSendButtonView.backgroundColor = inputColor
backgroundSendButtonView.translatesAutoresizingMaskIntoConstraints = false
return backgroundSendButtonView
}()
let sendButton = UIButton(type: .system)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
addSubview(uploadImageView)
// sendButton.setTitle("Send", for: UIControlState())
sendButton.setImage(UIImage(named: "planeChat"), for: .normal)
sendButton.backgroundColor = UIColor.white
sendButton.tintColor = UIColor(red: 82/255, green: 121/255, blue: 179/255, alpha: 1)
sendButton.layer.cornerRadius = 20
sendButton.clipsToBounds = true
sendButton.translatesAutoresizingMaskIntoConstraints = false
//what is handleSend?
addSubview(sendButton)
addSubview(self.inputTextField)
//x,y,w,h
// A enlever après
self.inputTextField.leftAnchor.constraint(equalTo: uploadImageView.rightAnchor, constant: 12).isActive = true
//self.inputTextField.leftAnchor.constraint(equalTo: leftAnchor, constant: 8).isActive = true
//self.inputTextField.centerYAnchor.constraint(equalTo: centerYAnchor).isActive = true
self.inputTextField.topAnchor.constraint(equalTo: topAnchor, constant: 4).isActive = true
self.inputTextField.rightAnchor.constraint(equalTo: sendButton.leftAnchor, constant: -4).isActive = true
self.inputTextField.heightAnchor.constraint(equalToConstant: 48).isActive = true
//x,y,w,h
sendButton.rightAnchor.constraint(equalTo: rightAnchor, constant: -8).isActive = true
sendButton.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
sendButton.widthAnchor.constraint(equalToConstant: 38).isActive = true
sendButton.heightAnchor.constraint(equalToConstant: 38).isActive = true
//x,y,w,h
uploadImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 18).isActive = true
uploadImageView.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
uploadImageView.widthAnchor.constraint(equalToConstant: 18).isActive = true
uploadImageView.heightAnchor.constraint(equalToConstant: 20).isActive = true
//l//et backgroundSendButtonView = UIView()
//addSubview(backgroundSendButtonView)
// backgroundSendButtonView.roundCorners([.topRight,.bottomRight], radius: 24)
insertSubview(backgroundSendButtonView, belowSubview: sendButton)
backgroundSendButtonView.rightAnchor.constraint(equalTo: rightAnchor, constant: -4).isActive = true
backgroundSendButtonView.centerYAnchor.constraint(equalTo: inputTextField.centerYAnchor).isActive = true
//backgroundSendButtonView.widthAnchor.constraint(equalToConstant: 30).isActive = true
backgroundSendButtonView.leftAnchor.constraint(equalTo: inputTextField.rightAnchor).isActive = true
backgroundSendButtonView.heightAnchor.constraint(equalTo: inputTextField.heightAnchor).isActive = true
//x,y,w,h
// let separatorLineView = UIView()
// separatorLineView.backgroundColor = UIColor(red: 220/255, green: 220/255, blue: 220/255, alpha: 1)
// separatorLineView.translatesAutoresizingMaskIntoConstraints = false
// addSubview(separatorLineView)
// //x,y,w,h
// separatorLineView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
// separatorLineView.topAnchor.constraint(equalTo: topAnchor).isActive = true
// separatorLineView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
// separatorLineView.heightAnchor.constraint(equalToConstant: 1).isActive = true
let gradientView = UIView()
let colorTop = UIColor.clear.cgColor
let colorBottom = UIColor(red: 0, green: 0, blue: 0, alpha: 0.05).cgColor
gradientView.translatesAutoresizingMaskIntoConstraints = false
addSubview(gradientView)
//x,y,w,h
gradientView.leftAnchor.constraint(equalTo: leftAnchor).isActive = true
gradientView.topAnchor.constraint(equalTo: topAnchor, constant: -25).isActive = true
gradientView.widthAnchor.constraint(equalTo: widthAnchor).isActive = true
gradientView.heightAnchor.constraint(equalToConstant: 25).isActive = true
gradientView.backgroundColor = UIColor.clear
let gradientBackground = CAGradientLayer()
gradientBackground.colors = [ colorTop, colorBottom]
gradientBackground.locations = [0.0, 1.0]
var backgroundLayer = CALayer()
backgroundLayer = gradientBackground
let width = UIScreen.main.bounds.size.width
backgroundLayer.frame = CGRect(x: 0, y: 0, width: width, height: 25)
print(backgroundLayer.frame)
print(gradientView.bounds)
gradientView.layer.insertSublayer(backgroundLayer, at: 0)
}
func setGradient(_ view: UIView, colorTop: CGColor, colorBottom: CGColor) {
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
chatLogController?.handleSend()
return true
}
#objc func textFieldDidChange(_ textField: UITextField) {
if textField == self.inputTextField {
if self.inputTextField.text!.isEmpty {
disableButton()
} else {
// sendButton.setTitleColor(typoGreyButton, for: .normal)
self.sendButton.isEnabled = true
self.sendButton.alpha = 1
}
}
}
func disableButton(){
//sendButton.setTitleColor(smoothGray, for: .normal)
sendButton.isEnabled = false
self.sendButton.alpha = 0.8
}
func emptyTextField() {
self.inputTextField.text = ""
disableButton()
}
// func textViewDidChange(_ textView: UITextView) {
// print(textView)
// if textView == self.inputContainerView.inputTextField {
// if (self.inputContainerView.inputTextField.text?.isEmpty)! {
// disableButton()
// } else {
// // sendButton.setTitleColor(typoGreyButton, for: .normal)
// self.inputContainerView.sendButton.isEnabled = true
// }
// }
//
// }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If you just want to anchor to the bottom safe area, you can do that anywhere in your view controller:
if #available(iOS 11.0, *) {
someView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true
} else {
someView.bottomAnchor.constraint(equalTo: bottomLayoutGuide.topAnchor).isActive = true
}
If, however, you want to use it as a constant (i.e. subtract the length of the bottom safe area from a value), you need to do that in a later lifecycle method like viewDidLayoutSubviews():
if #available(iOS 11.0, *) {
someView.bottomAnchor.constraint(equalTo: anotherView.bottomAnchor, constant: -view.safeAreaInsets.bottom).isActive = true
} else {
someView.bottomAnchor.constraint(equalTo: anotherView.bottomAnchor, constant: -bottomLayoutGuide.length).isActive = true
}
iOS 11 revamped their safe area API so make sure that you support pre-iOS-11 devices as I did in these examples.
I also just noticed that you've set the view's frame explicitly in its intializer. You typically don't want to set a view's frame like that if you're using auto layout (constraints). Therefore, I would suggest not setting the view's frame like you did and instead using constraints to do it.

How to sort UIView when keyboard appears?

I can't find a good solution of how to move UIView up when the keyboard appears. My solution now is this:
import UIKit
class LoginViewControllerbybys: UIViewController, UITextFieldDelegate {
// Create username and password text boxes
let emailTextField: UITextField = {
let field = UITextField()
field.placeholder = "EMAIL"
field.font = UIFont(name: "Helvetica Neue", size: 14)
field.background = UIImage(named: "divider")
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.spellCheckingType = .no
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
let passwordTextField: UITextField = {
let field = UITextField()
field.placeholder = "PASSWORD"
field.font = UIFont(name: "Helvetica Neue", size: 14)
field.background = UIImage(named: "divider")
field.autocapitalizationType = .none
field.autocorrectionType = .no
field.spellCheckingType = .no
field.isSecureTextEntry = true
field.translatesAutoresizingMaskIntoConstraints = false
return field
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white // sets background colour to white
//Adding notifies on keyboard appearing
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow(notification:)), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillHide(notification:)), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
// Adding Logo in the centre-top of the screen
let logo: UIImageView = {
let image = UIImage(named: "logo")
let imageView = UIImageView(image: image)
imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()
func logoView() {
logo.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
logo.heightAnchor.constraint(equalToConstant: 75).isActive = true
logo.widthAnchor.constraint(equalToConstant: 75).isActive = true
logo.topAnchor.constraint(equalTo: view.topAnchor, constant: 30).isActive = true
}
func emailTextFieldView() {
emailTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
emailTextField.heightAnchor.constraint(equalToConstant: 35).isActive = true
emailTextField.widthAnchor.constraint(equalToConstant: 310).isActive = true
emailTextField.topAnchor.constraint(equalTo: logo.bottomAnchor, constant: 50).isActive = true
}
func passwordTextFieldView() {
passwordTextField.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
passwordTextField.heightAnchor.constraint(equalToConstant: 35).isActive = true
passwordTextField.widthAnchor.constraint(equalToConstant: 310).isActive = true
passwordTextField.topAnchor.constraint(equalTo: emailTextField.bottomAnchor, constant: 30).isActive = true
}
// Create Sign In button
let signInButton: UIButton = {
let button = UIButton()
button.setImage(#imageLiteral(resourceName: "goButton"), for: .normal)
button.frame = CGRect(x: 0, y: 597, width: 375, height: 70)
button.addTarget(self, action: #selector(signInButtonTapped), for: .touchUpInside) // when tapped on Sign In button, execute function SignInTapped
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
func signInButtonView() {
signInButton.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
signInButton.heightAnchor.constraint(equalToConstant: 70).isActive = true
signInButton.widthAnchor.constraint(equalTo: view.widthAnchor, constant: 0).isActive = true
signInButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0).isActive = true
}
// Show all Subviews
self.view.addSubview(logo)
self.view.addSubview(signInButton)
self.view.addSubview(emailTextField)
self.view.addSubview(passwordTextField)
// Constraints
logoView()
signInButtonView()
emailTextFieldView()
passwordTextFieldView()
}
func keyboardWillShow(notification: NSNotification) {
let keyboardSize: CGSize = ((notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size)!
let offset: CGSize = ((notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue.size)!
if keyboardSize.height == offset.height {
if self.view.frame.origin.y == 0 {
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.frame.origin.y -= keyboardSize.height
})
}
} else {
UIView.animate(withDuration: 0.1, animations: { () -> Void in
self.view.frame.origin.y += keyboardSize.height - offset.height
})
}
}
func keyboardWillHide(notification: NSNotification) {
let keyboardSize: CGSize = ((notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size)!
self.view.frame.origin.y += keyboardSize.height
}
}
However, the problem with this is that it hides some of my boxes and a label and I can't see them. I thik I would be able to avoid it with UIScrollView but I have no idea how to do it... Would appreciate any help!
You can use UITableView, then you can focus on the selected row using:
scrollToRow(at:at:animated:)
https://developer.apple.com/reference/uikit/uitableview/1614997-scrolltorow

How to animate keyboard scrolling with keyboardDismissMode = .interactive

Im creating chat part of application and i have problem with keyboard animation when user is draging scrollview up and down. I am using keyboardDismissMode = .Interactive and i cant find notification working with it.
This is defaul state. Here i have UIView() user as container for Textview and Button.
and this is my problem when user just slowly scrolling down through the keyboard. I need to move with containerView when keyboard start moving.
I tried UIkeyboardwillChangeFrame but it didnt notificate.
he re sample of my code that i believe is usefull for you.
import UIKit
struct Message {
var reciever: String?
var sender: String?
var text: String?
var time: String?
}
class ChatMessagesVC: UIViewController, UITextViewDelegate,UIScrollViewDelegate {
var chatID: String?
var recieverName: String?
var recieverId: String?
var recieverPhoto: UIImage?
let scrollView: UIScrollView = UIScrollView()
let textView: UITextView = UITextView()
let sendButton: UIButton = UIButton()
var bottomConstraint: NSLayoutConstraint!
var lastMessageFrom: String = ""
var keyboardRect: CGRect!
let SENDER_BACKGROUND_COLOR: UIColor = .whiteColor()
let SENDER_TEXT_COLOR: UIColor = .blackColor()
let SENDER_FONT: UIFont = UIFont(name: "OpenSans", size: 13)!
let RECIEVER_BACKGROUND_COLOR: UIColor = .yellowColor()
let RECIEVER_TEXT_COLOR: UIColor = .blackColor()
let RECIEVER_FONT: UIFont = UIFont(name: "OpenSans", size: 13)!
// place values
var messageX: CGFloat = 75.0
var messageY: CGFloat = 26.0
var imageX: CGFloat = 10
override func viewDidLoad() {
super.viewDidLoad()
if self.recieverName != nil {
self.title = self.recieverName
}
if self.chatID != nil {
DataModel.instance.CHAT.childByAppendingPath(self.chatID).observeEventType(.ChildAdded, withBlock: {snap in
var message = Message(reciever: nil, sender: nil, text: nil, time: snap.key)
if let text = snap.value["text"] as? String {
message.text = text
}
if let sender = snap.value["from"] as? String {
message.sender = sender
}
if let reciever = snap.value["to"] as? String {
message.reciever = reciever
}
if let _ = snap.value["unread"] as? String {
if message.sender != currentUser.id {
DataModel.instance.CHAT.childByAppendingPath(self.chatID).childByAppendingPath(message.time).childByAppendingPath("unread").removeValue()
}
}
self.addMessage(message)
})
}
// notifications about keyboard
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillShow), name:UIKeyboardWillShowNotification, object: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(self.keyboardWillHide), name:UIKeyboardWillHideNotification, object: nil)
//BG
let backgroundView = UIImageView(frame: self.view.bounds)
let image = UIImage(named: "radarTable")
backgroundView.image = image
self.view.addSubview(backgroundView)
self.textView.delegate = self
self.view.addSubview(self.scrollView)
self.scrollView.backgroundColor = .clearColor()
self.scrollView.delegate = self
self.scrollView.scrollEnabled = true
self.scrollView.keyboardDismissMode = .Interactive
self.textView.font = UIFont(name: "OpenSans", size: 15)
self.textView.textColor = .whiteColor()
self.textView.text = NSLocalizedString("chat.placeholder", comment: "")
self.textView.backgroundColor = .clearColor()
self.textView.returnKeyType = .Send
self.sendButton.setTitleColor(.blackColor(), forState: .Normal)
self.sendButton.setTitle(NSLocalizedString("chat.send", comment: ""), forState: .Normal)
self.sendButton.titleLabel?.font = UIFont(name: "OpenSans", size: 15)
self.sendButton.backgroundColor = .orangeColor()
self.sendButton.addTarget(self, action: #selector(self.sendMessage), forControlEvents: .TouchUpInside)
// divider
let divider = UIView()
divider.backgroundColor = .yellowColor()
self.view.addSubview(divider)
// container
let containerView = UIView()
containerView.addSubview(self.textView)
containerView.addSubview(self.sendButton)
self.view.addSubview(containerView)
// layout
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
self.textView.translatesAutoresizingMaskIntoConstraints = false
self.sendButton.translatesAutoresizingMaskIntoConstraints = false
divider.translatesAutoresizingMaskIntoConstraints = false
containerView.translatesAutoresizingMaskIntoConstraints = false
let binding = ["scroll": self.scrollView, "text": self.textView, "button": self.sendButton, "div":divider, "container": containerView]
// horizontal constraints
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scroll]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[div]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[container]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[text]-5-[button(100)]-10-|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
// vertical constraints
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-50-[scroll]-0-[div(1)]-0-[container(50)]", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
containerView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[text]|", options: NSLayoutFormatOptions(), metrics: nil, views: binding))
containerView.addConstraint(NSLayoutConstraint(item: containerView, attribute: .CenterY, relatedBy: .Equal, toItem: self.sendButton, attribute: .CenterY, multiplier: 1, constant: 0))
containerView.addConstraint(NSLayoutConstraint(item: self.sendButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
self.bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1, constant: 0)
self.view.addConstraint(self.bottomConstraint)
}
//MARK: - textview Methods
func textViewDidBeginEditing(textView: UITextView) {
if textView.text == NSLocalizedString("chat.placeholder", comment: "") {
textView.text = nil
}
}
func textViewDidEndEditing(textView: UITextView) {
if textView.text.isEmpty {
textView.text = NSLocalizedString("chat.placeholder", comment: "")
}
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange, replacementText text: String) -> Bool {
if text == "\n" {
self.sendMessage()
return false
}
return true
}
//MARK: - scrollview functions
// func scrollViewDidScroll(scrollView: UIScrollView) {
// let location = scrollView.panGestureRecognizer.locationInView(self.view)
// if self.keyboardRect != nil {
// let start = UIScreen.mainScreen().bounds.height - self.keyboardRect.height
// if location.y > start {
// self.bottomConstraint.constant = -self.keyboardRect.height - (start - location.y)
//
// }
// }
// }
//MARK: - keyboard notifications
func keyboardWillShow(notification: NSNotification) {
if let keyboardSize = (notification.userInfo?[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.CGRectValue() {
self.keyboardRect = keyboardSize
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
self.bottomConstraint.constant = -keyboardSize.height
self.view.layoutIfNeeded()
}, completion: nil)
let scroll = CGPointMake(0, self.scrollView.contentSize.height - (self.scrollView.bounds.height))
self.scrollView.setContentOffset(scroll, animated: true)
}
}
func keyboardWillHide(notification: NSNotification) {
self.keyboardRect = nil
UIView.animateWithDuration(0.3, delay: 0.0, options: .CurveEaseInOut, animations: { () -> Void in
self.bottomConstraint.constant = 0
self.view.layoutIfNeeded()
}, completion: nil)
}
//MARK: - chat functions
func addMessage(message: Message) {
let theWidth = UIScreen.mainScreen().bounds.width
var messagesSpace:CGFloat = 2
if self.lastMessageFrom != message.sender {
//TODO: Dopln cas - neivem ako sa to mas presne spravat a zorbrazovat tak neimplementujem
messagesSpace += 25
}
let messageLbl : UILabel = UILabel()
messageLbl.frame = CGRectMake(0, 0, self.scrollView.frame.size.width - 100, 0)
messageLbl.lineBreakMode = NSLineBreakMode.ByWordWrapping
messageLbl.textAlignment = NSTextAlignment.Left
messageLbl.numberOfLines = 0
messageLbl.text = message.text
messageLbl.sizeToFit()
messageLbl.frame.origin.y = self.messageY + messagesSpace + 5
let frame = UIView()
if message.sender == currentUser.id {
messageLbl.backgroundColor = self.SENDER_BACKGROUND_COLOR
messageLbl.textColor = self.SENDER_TEXT_COLOR
messageLbl.font = self.SENDER_FONT
messageLbl.frame.origin.x = (self.scrollView.frame.size.width - self.messageX) - messageLbl.frame.width
} else {
messageLbl.backgroundColor = self.RECIEVER_BACKGROUND_COLOR
messageLbl.textColor = self.RECIEVER_TEXT_COLOR
messageLbl.font = self.SENDER_FONT
messageLbl.frame.origin.x = self.messageX
}
// if should add photo
if self.lastMessageFrom != message.sender {
let img:UIImageView = UIImageView()
img.frame = CGRectMake(self.imageX, self.messageY + messagesSpace, 50, 50)
self.lastMessageFrom = message.sender!
if message.sender == currentUser.id {
img.frame.origin.x = (self.scrollView.frame.size.width - self.imageX) - img.frame.size.width
img.image = currentUser.photo
} else {
img.image = self.recieverPhoto
}
img.layer.cornerRadius = img.frame.size.width/2
img.clipsToBounds = true
self.scrollView.addSubview(img)
}
let bounds = messageLbl.frame
frame.frame = CGRectMake(bounds.minX - 10, bounds.minY - 5, bounds.width + 14, bounds.height + 10)
frame.backgroundColor = messageLbl.backgroundColor
if message.sender == currentUser.id {
frame.roundCorners([.TopLeft, .BottomRight, .BottomLeft], radius: 10)
} else {
frame.roundCorners([.TopRight, .BottomRight, .BottomLeft], radius: 10)
}
self.scrollView.addSubview(frame)
self.scrollView.addSubview(messageLbl)
self.messageY += frame.frame.size.height + messagesSpace
self.scrollView.contentSize = CGSizeMake(theWidth, self.messageY + messagesSpace)
let bottomOfset:CGPoint = CGPointMake(0, self.scrollView.contentSize.height - self.scrollView.bounds.size.height)
self.scrollView.setContentOffset(bottomOfset, animated: true)
}
func scrollViewTapped() {
self.textView.resignFirstResponder()
}
func sendButtonPressed() {
textView.resignFirstResponder()
self.sendMessage()
}
func sendMessage() {
if self.textView.text != NSLocalizedString("chat.placeholder", comment: "") && !self.textView.text.isBlank {
let time = NSDate().ToUTCStringWithFormat("yyyy-MM-dd'T'HH:mm:ss")
let result = ["from":currentUser.id, "to": self.recieverId, "text": self.textView.text, "unread": "true"]
DataModel.instance.CHAT.childByAppendingPath(self.chatID).childByAppendingPath(time).setValue(result)
self.textView.text = nil
DataModel.instance.USERS.childByAppendingPath(currentUser.id).childByAppendingPath("chats").childByAppendingPath(self.chatID).setValue(self.recieverId)
DataModel.instance.USERS.childByAppendingPath(self.recieverId).childByAppendingPath("chats").childByAppendingPath(chatID).setValue(currentUser.id)
}
}
If I understand your question correctly, I think what you probably want here is to make your _containerView an inputAccessoryView as described in the Apple docs here:
https://developer.apple.com/library/ios/documentation/StringsTextFonts/Conceptual/TextAndWebiPhoneOS/InputViews/InputViews.html
Once you implement this, you'll get the keyboard behavior you describe because your view will be "attached" to the keyboard.

Resources