6 Replies
      Latest reply on Dec 22, 2019 2:57 PM by igorland
      igorland Level 1 Level 1 (0 points)

        Hi. I am trying to figure out how to do a simple thing, but all research that I have done so far has not clarified what the proper way to do it is. I am adding a UITextView that should expand while I am typing upwards and then enable scrolling when the maximum height is reached. Instead, my UITextView is expanding downwards. My code:

         

        overridefunc didMove(to view: SKView)
        {
        txtMessage = UITextView()
                txtMessage.font = UIFont.systemFont(ofSize: 16)
                txtMessage.layer.cornerRadius = 5
                txtMessage.autocorrectionType = UITextAutocorrectionType.no
                txtMessage.keyboardType = UIKeyboardType.default
                txtMessage.returnKeyType = UIReturnKeyType.done
                txtMessage.isScrollEnabled = false
                txtMessage.delegate = self
                self.view!.addSubview(txtMessage)
                registerForKeyboardNotifications()
            }
        
        func textViewDidChange(_ textView: UITextView)
            {
                let fixedWidth = textView.frame.size.width
              
                // Changing height of the message UITextView
                let newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT)))
                var newFrame = textView.frame
                newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
                txtMessage.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height))
           }
        
        func registerForKeyboardNotifications()
            {
                let notificationCenter = NotificationCenter.default
        
                notificationCenter.addObserver( self,
                                                selector: #selector(GameScene.keyboardWillShow(_:)),
                                                name: UIResponder.keyboardWillShowNotification,
                                                object: nil )
               
                notificationCenter.addObserver( self,
                                                selector: #selector(GameScene.keyboardWillBeHidden(_:)),
                                                name: UIResponder.keyboardWillHideNotification,
                                                object: nil)
            }
           
            func unregisterForKeyboardNotifications()
            {
                let notificationCenter = NotificationCenter.default
                notificationCenter.removeObserver(  self,
                                                    name: UIResponder.keyboardWillShowNotification,
                                                    object: nil)
               
                notificationCenter.removeObserver(  self,
                                                    name: UIResponder.keyboardWillHideNotification,
                                                    object: nil)
            }
           
            @objc func keyboardWillShow(_ notification: Notification)
            {
        
                let txtMessageViewHeight : CGFloat = 41
               
                if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
                {
                    let duration:TimeInterval = (notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
                    let animationCurveRawNSN = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
                    let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
                    let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
                   
                    keyboardHeight = keyboardFrame.cgRectValue.height
                    let viewHeight = keyboardHeight + txtMessageViewHeight
                   
                    self.txtMessage.frame.origin.y -= viewHeight
                    self.txtMessage.layoutIfNeeded()
                }
            }

         

        Thank you!

        • Re: Expand UITextView upwards from keyboard
          Claude31 Level 8 Level 8 (8,505 points)

          I read rapidly, but I think you were mislead by the inverse y coordinate issue. The origin is the top of TextView.

           

          If you do this:

                  let label1 = UITextView(frame: CGRect(x: 5, y: 80, width: 80, height: 20))
                  label1.text = "Label 1\nsuite"
                  label1.layer.borderColor = UIColor.black.cgColor
                  label1.layer.borderWidth = 1.0
                  let label2 = UITextView(frame: CGRect(x: 85, y: 80, width: 80, height: 40))
                  label2.text = "Label 2\nsuite"
                  label2.layer.borderColor = UIColor.black.cgColor
                  label2.layer.borderWidth = 1.0
                  self.view.addSubview(label1)
                  self.view.addSubview(label2)
          
          

          you get 2 UITextViews that are aligned at top.

           

          So, Change into

          let newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT))) 
           var newFrame = textView.frame
          newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
          var newOrigin = textView.frame.origin
          newOrigin.y -= (newSize.height - newFrame.size.height)
          txtMessage.frame = CGRect(origin: newOrigin, size: CGSize(width: newFrame.width, height: newFrame.height))
            • Re: Expand UITextView upwards from keyboard
              igorland Level 1 Level 1 (0 points)

              Thanks, Claude31, but nope. This does not address the issue. Exactly the same behavior. Cheers.

                • Re: Expand UITextView upwards from keyboard
                  Claude31 Level 8 Level 8 (8,505 points)

                  I am really surprised.

                  Line 6, why do you use

                  txtMessage.frame

                  and not textView.frame ?

                   

                   

                  Could you also draw the border to see what happens and add some print:

                   

                  func textViewDidChange(_ textView: UITextView)  {
                          let fixedWidth = textView.frame.size.width
                     
                          // Changing height of the message UITextView
                          let newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT)))
                          var newFrame = textView.frame
                          print("textView.frame", textView.frame)
                  
                          newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
                          var newOrigin = textView.frame.origin
                          newOrigin.y -= (newSize.height - newFrame.size.height)
                  
                          textView.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height))
                          print("New textView.frame", textView.frame)
                          txtMessage.layer.borderColor = UIColor.red.cgColor
                          txtMessage.layer.borderWidth = 1.0
                  
                     }
              • Re: Expand UITextView upwards from keyboard
                igorland Level 1 Level 1 (0 points)

                Hello.

                 

                I think, I know why I had issues with my code. Looks like the textViewDidChange method was also influenced by didChangeSize(oldSize:) method which was also called every time I typed in a character and which I needed to handle rotation of my device.

                 

                Anyways, I think I have come up with a solution how to handle expanding a textView which also gets adjusted with a rotation. Here is the code for the peer review.

                 

                import UIKit
                
                class ViewController: UIViewController, UITextViewDelegate
                {
                   
                    var textView: UITextView!
                    var btn: UIButton!
                    var btnSend: UIButton!
                    var keyboardHeight : CGFloat = 0
                    var isKeyboardShown = false
                    var textViewHeight : CGFloat = 0
                    var textView_inset : CGFloat = 10
                    var textView_insetBottom : CGFloat = 5
                   
                    let btnSendWidth : CGFloat = 50
                    let btnSendHeight : CGFloat = 50
                
                   
                   
                    override func viewDidLoad()
                    {
                        super.viewDidLoad()
                       
                        textViewHeight = 35
                
                       
                        textView = UITextView(frame: CGRect(x: textView_inset+self.view.safeAreaInsets.left,
                                                            y: self.view!.bounds.height,
                                                            width: self.view.frame.width-textView_inset*3-self.view.safeAreaInsets.left-self.view.safeAreaInsets.right-btnSendWidth,
                                                            height: textViewHeight))
                       
                        textView.textAlignment = NSTextAlignment.justified
                        textView.backgroundColor = UIColor.lightGray
                       
                        // Use RGB colour
                        textView.backgroundColor = UIColor(red: 39/255, green: 53/255, blue: 182/255, alpha: 1)
                       
                        // Update UITextView font size and colour
                        textView.font = UIFont(name: "Verdana", size: 17)
                        textView.textColor = UIColor.white
                
                       
                        // Make UITextView web links clickable
                        textView.isSelectable = true
                        textView.isEditable = true
                        textView.dataDetectorTypes = UIDataDetectorTypes.link
                        textView.isScrollEnabled = false
                       
                       
                        // Make UITextView corners rounded
                        textView.layer.cornerRadius = 10
                       
                        // Enable auto-correction and Spellcheck
                        textView.autocorrectionType = UITextAutocorrectionType.yes
                        textView.spellCheckingType = UITextSpellCheckingType.yes
                        // myTextView.autocapitalizationType = UITextAutocapitalizationType.None
                       
                       
                        self.view.addSubview(textView)
                        textView.delegate = self
                       
                        registerForKeyboardNotifications()
                       
                       
                        btn = UIButton()
                        btn.setTitle("PRESS", for: .normal)
                        btn.setTitleColor(.red, for: .normal)
                        btn.frame = CGRect(x: 50, y: 150, width: 100, height: 50)
                        btn.addTarget(self, action: #selector(self.showTextView(sender:)), for: .touchUpInside)
                        self.view.addSubview(btn)
                       
                       
                       
                        btnSend = UIButton()
                        btnSend.setTitle("SEND", for: .normal)
                        btnSend.setTitleColor(.white, for: .normal)
                        btnSend.backgroundColor = .red
                        btnSend.frame = CGRect(x: self.view.frame.width-self.view.safeAreaInsets.right-btnSendWidth-textView_inset,
                                               y: self.view!.bounds.height,
                                               width: btnSendWidth,
                                               height: btnSendHeight)
                        btnSend.addTarget(self, action: #selector(self.sendMessage(sender:)), for: .touchUpInside)
                        self.view.addSubview(btnSend)
                
                    }
                
                   
                    override func viewWillLayoutSubviews()
                    {
                        var textView_y : CGFloat = 0
                        var btnSend_y : CGFloat = 0
                       
                        // Required to ensure that the TextView's height is changed dynamically!
                        if isKeyboardShown
                        {
                            textView_y = self.view!.bounds.height-keyboardHeight-textView.frame.height-textView_insetBottom
                            btnSend_y = self.view!.bounds.height-keyboardHeight-btnSendHeight-textView_insetBottom
                        }
                        else
                        {
                            textView_y = self.view!.bounds.height
                            btnSend_y = self.view!.bounds.height
                        }
                       
                        textView.frame = CGRect(x: textView_inset+self.view.safeAreaInsets.left,
                                                y: textView_y,
                                                width: self.view.frame.width-textView_inset*3-self.view.safeAreaInsets.left-self.view.safeAreaInsets.right-btnSendWidth,
                                                height: textViewHeight)
                       
                        btnSend.frame = CGRect(x: self.view.frame.width-self.view.safeAreaInsets.right-self.btnSendWidth-textView_inset,
                                               y: btnSend_y,
                                               width: btnSendWidth,
                                               height: btnSendHeight)
                    }
                   
                   
                    @objc func showTextView(sender: UIButton!)
                    {
                        textView.becomeFirstResponder()
                    }
                   
                   
                    @objc func sendMessage(sender: UIButton!)
                    {
                        print("------SEND MESSAGE-------")
                    }
                   
                
                    func textViewDidChange(_ textView: UITextView)
                    {
                        let fixedWidth = textView.frame.size.width
                             
                        // Changing height of the message UITextView
                        var newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT)))
                       
                       
                        // Limit the height to 100
                        if newSize.height > 100
                        {
                            newSize.height = 100
                            textView.isScrollEnabled = true
                        }
                        else
                        {
                            textView.isScrollEnabled = false
                        }
                       
                       
                        var newFrame = textView.frame
                        newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
                       
                       
                        textViewHeight = newFrame.height
                       
                        var newOrigin = textView.frame.origin
                       
                        newOrigin.y -= (newSize.height - newFrame.size.height)
                          
                        textView.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height))
                    }
                
                      
                    func registerForKeyboardNotifications()
                    {
                        let notificationCenter = NotificationCenter.default
                          
                        notificationCenter.addObserver( self,
                                                        selector: #selector(ViewController.keyboardWillShow(_:)),
                                                        name: UIResponder.keyboardWillShowNotification,
                                                        object: nil )
                          
                        notificationCenter.addObserver( self,
                                                        selector: #selector(ViewController.keyboardWillBeHidden(_:)),
                                                        name: UIResponder.keyboardWillHideNotification,
                                                        object: nil)
                    }
                      
                
                    func unregisterForKeyboardNotifications()
                    {
                        let notificationCenter = NotificationCenter.default
                        notificationCenter.removeObserver(  self,
                                                            name: UIResponder.keyboardWillShowNotification,
                                                            object: nil)
                          
                        notificationCenter.removeObserver(  self,
                                                            name: UIResponder.keyboardWillHideNotification,
                                                            object: nil)
                    }
                      
                
                    @objc func keyboardWillShow(_ notification: Notification)
                    {
                        isKeyboardShown = true
                       
                        // Reset the position of the textview to the bottom of the screen
                        textView.frame = CGRect(x: textView_inset+self.view.safeAreaInsets.left,
                                                y: self.view!.bounds.height,
                                                width: self.view.frame.width-textView_inset*3-self.view.safeAreaInsets.left-self.view.safeAreaInsets.right-btnSendWidth,
                                                height: textViewHeight)
                       
                        btnSend.frame = CGRect(x: self.view.frame.width-self.view.safeAreaInsets.right-btnSendWidth-textView_inset,
                                               y: self.view!.bounds.height,
                                               width: btnSendWidth,
                                               height: btnSendHeight)
                       
                       
                        if let keyboardFrame: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
                        {
                            keyboardHeight = keyboardFrame.cgRectValue.height
                           
                            // Place the TextView above the keyboard
                            self.textView.frame.origin.y -= (keyboardHeight+self.textView.frame.height+textView_insetBottom)
                           
                            // Determine a new TextView height dynamically
                            let fixedWidth = textView.frame.size.width // Width never changes
                            var newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT))) // Height is based on the number of characters
                           
                           
                            // Limit the height to 100
                            if newSize.height > 100
                            {
                                newSize.height = 100
                                textView.isScrollEnabled = true
                            }
                            else
                            {
                                textView.isScrollEnabled = false
                            }
                           
                           
                
                            var newFrame = textView.frame // A new frame: Fixed width; Dynamically changed height
                            newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height)
                            textViewHeight = newFrame.height // Assign the height value to the object property
                           
                            textView.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height))
                           
                            self.textView.layoutIfNeeded()
                           
                           
                            self.btnSend.frame.origin.y -= (keyboardHeight+self.btnSend.frame.height+textView_insetBottom)
                            btnSend.frame = CGRect(origin: btnSend.frame.origin, size: CGSize(width: btnSendWidth, height: btnSendHeight))
                        }
                    }
                
                
                    @objc func keyboardWillBeHidden(_ notification : NSNotification)
                    {
                        isKeyboardShown = false
                       
                        if let _: NSValue = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue
                        {
                              
                            //let newHeight: CGFloat
                            let duration:TimeInterval = (notification.userInfo![UIResponder.keyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0
                            let animationCurveRawNSN = notification.userInfo![UIResponder.keyboardAnimationCurveUserInfoKey] as? NSNumber
                            let animationCurveRaw = animationCurveRawNSN?.uintValue ?? UIView.AnimationOptions.curveEaseInOut.rawValue
                            let animationCurve:UIView.AnimationOptions = UIView.AnimationOptions(rawValue: animationCurveRaw)
                              
                            keyboardHeight = 0
                
                            UIView.animate(withDuration: duration,
                                           delay: TimeInterval(0),
                                           options: animationCurve,
                                           animations: {
                                            self.textView.frame.origin.y = self.view!.bounds.height
                                            self.textView.layoutIfNeeded()
                                            self.btnSend.frame.origin.y = self.view!.bounds.height
                                            },
                                           completion: nil)
                        }
                          
                    }
                
                
                    func textViewDidBeginEditing(_ textView: UITextView)
                    {
                        textView.becomeFirstResponder()
                       
                        let tapRecognizer = UITapGestureRecognizer(target: self,
                                                                      action: #selector(tapDetected(_:)))
                          
                        if self.view != nil {self.view!.addGestureRecognizer(tapRecognizer)}
                    }
                      
                      
                
                    func textViewDidEndEditing(_ textView: UITextView)
                    {
                        // Make the active field nil to hide the keyboard
                        //self.activeField = nil;
                    }
                      
                
                    @objc func tapDetected(_ tapRecognizer: UITapGestureRecognizer)
                    {
                        textView?.resignFirstResponder()
                        if self.view != nil {self.view!.removeGestureRecognizer(tapRecognizer)}
                    }
                
                }

                 

                Thanks!

                  • Re: Expand UITextView upwards from keyboard
                    Claude31 Level 8 Level 8 (8,505 points)

                    In your code that works:

                     

                       func textViewDidChange(_ textView: UITextView) 
                        { 
                            let fixedWidth = textView.frame.size.width 
                                  
                            // Changing height of the message UITextView 
                            var newSize = textView.sizeThatFits(CGSize.init(width: fixedWidth, height: CGFloat(MAXFLOAT))) 
                            
                            
                            // Limit the height to 100 
                            if newSize.height > 100 
                            { 
                                newSize.height = 100 
                                textView.isScrollEnabled = true 
                            } 
                            else 
                            { 
                                textView.isScrollEnabled = false 
                            } 
                            
                            var newFrame = textView.frame 
                            newFrame.size = CGSize.init(width: CGFloat(fmaxf(Float(newSize.width), Float(fixedWidth))), height: newSize.height) 
                            
                            
                            textViewHeight = newFrame.height 
                            
                            var newOrigin = textView.frame.origin 
                            newOrigin.y -= (newSize.height - newFrame.size.height) 
                               
                            textView.frame = CGRect(origin: textView.frame.origin, size: CGSize(width: newFrame.width, height: newFrame.height)) 
                        } 

                    It seems that newOrigin is never used.

                     

                    Why don't you use line 29 ?