Stefan
Stefan

Reputation: 936

update nslayoutconstraints to accommodate new view size?

I have a UIView that increases in size once the text in a UITextView reaches a certain point. Both views are resizing, however I can't get the view to move upwards to accommodate the bottomconstraint I set on the UIView. Here's a screenshot of the issue.

before the view is resized

after the view is resized

I have tried using both view.setNeedsLayout() and view.layoutIfNeeded() but I'm assuming I might be implementing them wrong...

Here's the code for the project

import UIKit

class ViewController: UICollectionViewController, UICollectionViewDelegateFlowLayout, UITextViewDelegate {


var bottomConstraint: NSLayoutConstraint?
var phconstraint: NSLayoutConstraint?

let searchBarContainer: UIView = {
    let sBarContainer = UIView()
    sBarContainer.backgroundColor = UIColor.gray
    sBarContainer.layer.cornerRadius = 3
    return sBarContainer
}()

let searchBar: UITextView = {
    let sBar = UITextView()
    sBar.textAlignment = .left
    sBar.font = .systemFont(ofSize: 12)
    sBar.backgroundColor = UIColor.red
    sBar.sizeToFit()

    return sBar
}()

let placeholder: UILabel = {
    let pholder = UILabel()

    pholder.font = UIFont.systemFont(ofSize: 16, weight: 0.20)
    pholder.frame.size = pholder.intrinsicContentSize
    pholder.backgroundColor = UIColor.clear
    pholder.textColor = UIColor.gray

    pholder.text = "Share!"

    pholder.textAlignment = .left


    return pholder
}()

let dividerLine: UIView = {
    let line = UIView()
    line.backgroundColor = UIColor.init(red: 240/255, green: 240/255, blue: 240/255, alpha: 1)
    return line
}()

let bubbleview : UIView = {
   let bView = UIView()
    bView.backgroundColor = UIColor.clear
    return bView

}()

func setupKeyboardObservers() {

    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)

    NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
}

func handleKeyboardNotification(_ notification: Notification) {

    if let userInfo = notification.userInfo {
        let keyboardFrame = (userInfo[UIKeyboardFrameEndUserInfoKey] as AnyObject).cgRectValue
        print(keyboardFrame as Any)

        let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
        let isKeybobardNotShowing = notification.name == NSNotification.Name.UIKeyboardWillHide

        bottomConstraint?.constant = isKeyboardShowing ? -keyboardFrame!.height : 0

        UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {

            self.view.layoutIfNeeded()
        }, completion: { (completed) in


        })
    }
}


override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = "Search Bar"
    collectionView?.backgroundColor = UIColor.white

    setupKeyboardObservers()
    self.searchBar.delegate = self    
    view.addSubview(searchBarContainer)
    searchBarContainer.addSubview(searchBar)
    searchBar.translatesAutoresizingMaskIntoConstraints = false
    searchBarContainer.translatesAutoresizingMaskIntoConstraints = false

    NSLayoutConstraint(item: searchBarContainer, attribute: .width, relatedBy: .equal, toItem: view, attribute: .width, multiplier: 0.75, constant: 0).isActive = true

    NSLayoutConstraint(item: searchBarContainer, attribute: .height, relatedBy: .equal, toItem: view, attribute: .height, multiplier: 0.05, constant: 0).isActive = true

    NSLayoutConstraint(item: searchBarContainer, attribute: .centerX, relatedBy: .equal, toItem: view, attribute: .centerX, multiplier: 1, constant: 0).isActive = true

     bottomConstraint = NSLayoutConstraint(item: searchBarContainer, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)

            view.addConstraint(bottomConstraint!)

}


func textViewDidChange(_ textView: UITextView) {
            let currentString: String = textView.text!
            let length: Int = (currentString.characters.count )

        let messageText = currentString
        let size = CGSize(width: searchBarContainer.frame.width , height: searchBarContainer.frame.height * 2)
        let options = NSStringDrawingOptions.usesFontLeading.union(.usesLineFragmentOrigin)
        let estimatedFrame = NSString(string: messageText).boundingRect(with: size, options: options, attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 12)], context: nil)
    print("this is the ewidth \(estimatedFrame.width)")
    searchBarContainer.addSubview(bubbleview)
    bubbleview.frame = estimatedFrame

    searchBar.sizeToFit()
    searchBar.frame.size.width = searchBarContainer.frame.width


            if estimatedFrame.height >= (searchBarContainer.frame.height) {

            searchBarContainer.frame.size.height = estimatedFrame.height


                self.view.addConstraint(self.bottomConstraint!)

                UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {

                    self.view.layoutIfNeeded()
                }, completion: { (completed) in



                })


        }
    view.setNeedsLayout()
    view.layoutIfNeeded()

}

}


extension UIView {
func addConstraintsWithFormat(_ format: String, views : UIView...) {


    var viewsDictionary = [String: UIView]()

    for(index, view) in views.enumerated(){
        let key = "v\(index)"
        viewsDictionary[key] = view
        view.translatesAutoresizingMaskIntoConstraints = false

    }
    addConstraints(NSLayoutConstraint.constraints(withVisualFormat: format, options: NSLayoutFormatOptions(), metrics: nil, views: viewsDictionary))

}
}

Upvotes: 2

Views: 529

Answers (3)

DonMag
DonMag

Reputation: 77546

Couple notes...

When trying to use an "auto-sizing" UITextView you will find it works best if you disable scrolling... so add this to your searchBar setup:

    sBar.isScrollEnabled = false

You are mixing auto-layout / constraints with explicitly setting frame sizes. That frequently gets you in trouble. Use one or the other (with constraints being the preferred method). In your code, you set

    searchBar.translatesAutoresizingMaskIntoConstraints = false

But you never give it any constraints.

Edit: After playing around a bit, you can greatly simplify things just by relying on auto-layout.

Instead of using a "container" view to hold the searchBar text view, create a "padding" view that will sit underneath searchBar. Then:

  1. searchBar should sit "on top of" paddingView
  2. paddingView should slide up with the keyboard
  3. searchBar will automatically slide up, because it's pinned to the top of paddingView
  4. searchBar's height will automatically change based on text content

So -- paddingView will control the position, width and Y-position.

You already have a property to control the Y-position

var bottomConstraint: NSLayoutConstraint?

Change searchBar definition and add paddingView definition:

let searchBar: UITextView = {
    let sBar = UITextView()
    sBar.textAlignment = .left
    sBar.font = .systemFont(ofSize: 12)
    sBar.backgroundColor = UIColor.red
    sBar.isScrollEnabled = false
    sBar.translatesAutoresizingMaskIntoConstraints = false
    return sBar
}()

let paddingView: UIView = {
    let v = UIView()
    v.translatesAutoresizingMaskIntoConstraints = false
    v.backgroundColor = .gray
    v.layer.cornerRadius = 3.0
    return v
}()

viewDidLoad ends up like this:

override func viewDidLoad() {
    super.viewDidLoad()
    navigationItem.title = "Search Bar"
    collectionView?.backgroundColor = UIColor.white
    setupKeyboardObservers()
    self.searchBar.delegate = self

    // add bottom "padding" view
    view.addSubview(paddingView)

    // add searchBar text view
    view.addSubview(searchBar)

    // set width of padding view to 75% of view width
    paddingView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.75).isActive = true
    // set center X of padding view to center X of view
    paddingView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 1.0).isActive = true
    // set height of padding view to 6.0 (we're rounding the corners with a radius of 3.0)
    paddingView.heightAnchor.constraint(equalToConstant: 6.0).isActive = true

    // assign a constraint to bottomConstraint property so we can change the Y-position of padding view (and searchBar) when desired
    // initially set the constant to 0 (so it sits on the bottom of the view)
    bottomConstraint = paddingView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: 0.0)
    bottomConstraint?.isActive = true

    // set searchBar width to padView width
    searchBar.widthAnchor.constraint(equalTo: paddingView.widthAnchor, multiplier: 1.0).isActive = true
    // set searchBar center X to padView center X
    searchBar.centerXAnchor.constraint(equalTo: paddingView.centerXAnchor, constant: 0.0).isActive = true
    // set searchBar bottom to padView Top + 3.0 (we want to cover the top 3 pts)
    searchBar.bottomAnchor.constraint(equalTo: paddingView.topAnchor, constant: 3.0).isActive = true
}

Since UITextView auto-adjusts its own size when using auto-layout, you don't even need the textViewDidChange function:

func textViewDidChange(_ textView: UITextView) {
    // nothing to do here...
}

enter image description here

enter image description here

Note: All I could see from your bubbleview was that it covered a portion of the searchBar text view... so I ignored it.

Upvotes: 2

Jože Ws
Jože Ws

Reputation: 1814

Update textView layout after calling sizeToFit()

searchBar.sizeToFit()
searchBar.frame.size.width = searchBarContainer.frame.width
searchBar.setNeedsLayout()

Upvotes: 0

elk_cloner
elk_cloner

Reputation: 2149

Just use stackview.

  1. That is wrap your textView to a stackView
  2. pull necessary layout constraint for stackView.
  3. Make your TextView non scrollable from attribute inspector. Your textView will increase upward when it's text increases.

Upvotes: 0

Related Questions