UITextField layout superview after appending text - ios

I have UITableView with 2 UITextField elements in each row placed horizontally.
I want right UITextField automatically enlarge when I append text, but layout only happens when I end editing text.
I tried adding this code to UITableViewCell but it doesn't work
override func awakeFromNib() {
super.awakeFromNib()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(BillPositionCell.textDidChange), name: UITextFieldTextDidChangeNotification, object: self.sumTextField)
}
func textDidChange() {
self.sumTextField.setNeedsLayout()
self.sumTextField.layoutIfNeeded()
self.contentView.setNeedsLayout()
self.contentView.layoutIfNeeded()
}

Try:(width is sumTextField width NSLayoutConstraint)
func textDidChange() {
let newSize = sumTextField.sizeThatFits(CGSizeMake(CGFloat.max, CGFloat.max))
width.constant = newSize.width
layoutIfNeeded()
}

See this answer: textDidChange vs controlTextDidChange
In short, use controlTextDidChange: instead.

Related

How to move the view when the keyboard presents with one textview but not another one

I have a UITextView at the top, a UITextView in the centre and a UITextView at the bottom.
I want to move the view up when the keyboard presents if using the bottom UITextView or the centre UITextView but when using the top UITextView the view shouldn't move.
How do I make this work?
func showLoginKeyBoard()
{
NotificationCenter.default.addObserver(self, selector: #selector(keyboardWillShow), name: UIResponder.keyboardWillShowNotification, object: nil)
}
#objc func keyboardWillShow(notification: NSNotification)
{
if let keyboardSize = (notification.userInfo?[UIResponder.keyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue
{
if self.view.frame.origin.y == 0
{
self.view.frame.origin.y -= keyboardSize.height
}
}
}
func textViewDidBeginEditing(_ textView: UITextView)
{
if textView == centreTextView
{
showLoginKeyBoard()
}
if textView == bottomTextView
{
showLoginKeyBoard()
}
}
Currently when any of the UITextViews becomeFirstResponder the view moves up which means when using the top UITextView it isn't visible.
How can I make sure the top UITextView doesn't move the view up?
Before answer your question ,
According to your code every time user click on textView you add Observer.Don't do this. Add observers at viewDidLoad() and don’t forget to remove observers in viewDidDisappear(). Otherwise it will cause to memory leaks.
Now,Answer to question
define fileprivate optional textView
var currentTextView:UITextView?
Then assign textField in textViewDidBeginEditing
func textViewDidBeginEditing(_ textView: UITextView){
currentTextView = textView
}
now you can show or not according to currentTextView
#objc func keyboardWillShow(notification: NSNotification){
if let txtView = currentTextView{
txtView != topTextView {
//move up the view
}
}
}
I recommend using IQKeyboardManager library which handles all your keyboard events with only a single line of code

Resizeable then scrollable UITextView like Telegram

I want to create UITextView that can resize and scroll at the same time like the ones on Telegram , Instagram or Whats App that allow UITextView to grow up to or 8 lines then you can scroll if you add more text to it I was able to make the UITextView grow to 5 line but if they are more text I can not see since the isScroll property is disabled
my UITextView is inside UIView with two button on the left and right and I would prefer to do it through constrain if that's possible if not through code is fine too
Sagar's answer is great, but I want to enhance it a bit and add some animation to it:
the steps you need
get an outlet to your textView
add a height constraint and get an outlet to it
implement textViewDidChange delegate method of the textView
in textViewDidChange
calculate new height using textView.sizeThatFits(size)
set the height constraint constant to new height
[optional] animate the constraint change to be more user friendly
here is an example
class ViewController: UIViewController {
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var textViewHeightConstraint: NSLayoutConstraint!
let maxTextHeight:CGFloat = 200
let minTextHeight:CGFloat = 50
let animationDuration:Double = 0.3
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
resizeTextViewToFitText()
}
func resizeTextViewToFitText() {
let size = CGSize(width: textView.frame.width, height: .infinity)
let expectedSize = textView.sizeThatFits(size)
self.textViewHeightConstraint.constant = max(min(expectedSize.height, self.maxTextHeight), self.minTextHeight)
self.textView.isScrollEnabled = expectedSize.height > self.maxTextHeight
UIView.animate(withDuration: animationDuration) {
self.view.layoutIfNeeded()
}
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
resizeTextViewToFitText()
}
}
You can achieve your expected outcome by following steps:
Assign a textView delegate to your controller
Default disable textView scrolling
On textViewDidChange delegate method measure text height according textView frame
Assign appropriate height to textview & enable scroll if content is exceeded (Up to max height in your case 8 line)
Here below I am attaching code snippet, which may help you:
let commentViewMinHeight: CGFloat = 45.0
let commentViewMaxHeight: CGFloat = 120.0 //In your case it should be 8 lines
func textViewDidChange(_ textView: UITextView) {
//Calculate text height
let size = textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
textViewHeightConstraint.constant = size.height.clamped(to: commentViewMinHeight...commentViewMaxHeight)
if textView.contentSize.height < commentViewMaxHeight {
textView.setContentOffset(CGPoint.zero, animated: false)
if textView.isScrollEnabled {
textView.isScrollEnabled = false
}
} else {
if !textView.isScrollEnabled {
textView.isScrollEnabled = true
}
}
}
extension Comparable {
func clamped(to limits: ClosedRange<Self>) -> Self {
return min(max(self, limits.lowerBound), limits.upperBound)
}
}

How do I get UITextView field to expand when adding text and scrolling is disabled?

I am using a UITextView inside a tableView cell to hold varying sized text content with scrolling disabled.
In order to auto-size the UITextView I've used auto-layout to pin it to the layout and also added this method to adjust the height:
override func viewWillAppear(_ animated: Bool) {
tableView.estimatedRowHeight = 50
tableView.rowHeight = UITableViewAutomaticDimension
}
This works correctly on the initial view - when the content first loads. However, I also want the user to be able to edit the text when they tap into the content (similar to the Apple Reminders app). This works correctly with one limitation: UITextView does not expand as the content grows.
How do I enable UITextView to expand during editing without scrolling?
New details:
Here is a screenshot of the current settings.
Per Matt's recommendations below, I created the following subclass.
class MyTextView: UITextView {
#IBOutlet var heightConstraint : NSLayoutConstraint?
override func awakeFromNib() {
super.awakeFromNib()
self.heightConstraint?.isActive = false
}
}
I had to modify the forced unwrapping to avoid a fatal error.
How do I enable UITextView to expand during editing without scrolling
A self-sizing text view is very simple; for a non-scrolling text view with no height constraint, it's the default. In this example, I've added some code to remove the existing height constraint, though you could do that in the storyboard just by indicating that the height constraint is a placeholder:
class MyTextView : UITextView {
#IBOutlet var heightConstraint : NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
self.heightConstraint.isActive = false
}
}
Screencast of the result:
If you subsequently do a batch update on the table view, and assuming the cell's other internal constraints are right, the cell will be remeasured as well (but I didn't demonstrate that as part of the example).
Everyone was very diligent about trying to help me resolve this issue. I tried each one and was not able to implement any of them with satisfactory results.
I was directed to this solution by an associate: https://stackoverflow.com/a/36070002/152205 and with the following modifications was able to solve my problem.
// MARK: UITextViewDelegate
func textViewDidChange(_ textView: UITextView) {
let startHeight = textView.frame.size.height
let calcHeight = textView.sizeThatFits(textView.frame.size).height
if startHeight != calcHeight {
UIView.setAnimationsEnabled(false)
self.tableView.beginUpdates()
self.tableView.endUpdates()
// let scrollTo = self.tableView.contentSize.height - self.tableView.frame.size.height
// self.tableView.setContentOffset(CGPoint(x: 0, y: scrollTo), animated: false)
UIView.setAnimationsEnabled(true)
}
}
Note: The scrollTo option caused the content to shift up several cell. With that removed everything worked as expected.
you could use var sizeThatFits
override func viewDidLoad() {
super.viewDidLoad()
textView = UITextView()
textView.sizeThatFits(CGSize(width: textView.frame.size.width, height: textView.frame.size.height))
}

how to move inputView above keyboard when scrolling the table

I have a view controller in which it has
table view and textView below the table
When I tap on the textView the keyboard appears and when I tap outside the textView the keyboard disappar
That works fine but I have one problem which is when I Drag(scroll Down) the table view the textView doesn't move down with the keyboard and I see black background behind the keyboard as in the below image
How to solve this problem in Swift
Update:
This is my current code that observe the keyboardFrameChanges
func keyboardFrameChanged(notification: NSNotification) {
let dict = NSDictionary(dictionary: notification.userInfo!)
let keyboardValue = dict.objectForKey(UIKeyboardFrameEndUserInfoKey) as! NSValue
let bottomDistance = mainScreenSize().height - keyboardValue.CGRectValue().origin.y
let duration = Double(dict.objectForKey(UIKeyboardAnimationDurationUserInfoKey) as! NSNumber)
UIView.animateWithDuration(duration, animations: {
self.inputViewConstraint!.constant = -bottomDistance
self.view.layoutIfNeeded()
}, completion: {
(value: Bool) in
self.chatTableView.scrollToBottom(animation: true)
})
}
Update 2:
I found a solution by changing the keyboardDismissMode from Interactive to OnDrag
chatTableView.keyboardDismissMode = UIScrollViewKeyboardDismissMode.OnDrag
This will move the keyboard and the textView immediatly to down once it observe any drag movement in the table , But how to do it in the Interactive mode like in the whatsapp chat view
set you scroll view (UITableView or UICollectionView) frame equal to you UIViewController frame
set .interactive to keyboardDismissMode of you scroll view
override canBecomeFirstResponder and return true
setup input container with UITextField or UITextView in screen bottom
override inputAccessoryView and return input container
enjoy
See my simple code here on github
If you implement your own bottom constraint for input view
And change its constant on the keyboard frame notifications. Using these lines of code, the error of predictive bar layout at the end of the animation will be fixed.
override var inputAccessoryView: UIView? {
return UIView()
}
override var canBecomeFirstResponder: Bool {
return true
}
Finding the answer for this took forever but this is the correct way of implementing this feature with the interactive state enabled.
In your view controller that has the tableview or collection view displaying chat bubbles override inputAccessoryView, then return the view that contains the uitextView or uitextField. and override canBecomeFirstResponder then return true.
override var inputAccessoryView: UIView? {
get {
self.inputBar.frame.size.height = self.barHeight // 50.0
self.inputBar.clipsToBounds = true
return self.inputBar
}
}
override var canBecomeFirstResponder: Bool{
return true
}
You could observe the frame of the keyboard using the NSNotificationCenter and create a method which will be called when the frame changes. In this method you get the coordinates of the keyboard and can reposition your inputView accordingly.
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.keyboardDidChangeFrame(_:)), name: UIKeyboardWillChangeFrameNotification, object: nil)
}
func keyboardDidChangeFrame(notification: NSNotification) {
let keyboardScreenFrameEnd = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue()
let keyboardViewFrameEnd = view.convertRect(keyboardScreenEndFrame, fromView: view.window)
let keyboardHeight = keyboardViewFrameEnd.height
// calculate the offset of your inputView
inputView.frame = CGRectOffset(inputView?.frame, ..., ...)
}

Stop cell from animating

In my tableView I'm using custom cells with two custom labels. Eventually the text of these labels will change but at the same time, layoutIfNeeded() is called to animate some other things. This is causing the text in the labels to animate as well which I'm trying to prevent.
The code I'm using to change the text:
func tapped(recognizer: UIGestureRecognizer) {
let path = NSIndexPath(forRow: myData.count - 1, inSection: 0)
let cell = tableView.cellForRowAtIndexPath(path) as! CustomCell
cell.infoLabel.text = "Changing text here"
UIView.animateWithDuration(0.5) { self.anotherView.layoutIfNeeded() }
}
To prevent the labels from animating I have tried adding this in CustomLabel and CustomCell, as well as in willDisplayCell:
override func layoutSubviews() {
UIView.performWithoutAnimation { super.layoutSubviews() }
}
Or using another way of preventing it to animate in CustomLabel and CustomCell:
override func layoutSubviews() {
CATransaction.begin()
CATransaction.setDisableActions(true)
super.layoutSubviews()
CATransaction.commit()
}
I've also tried a property observer setting the text and removing all animations at the same time:
var text: String {
didSet {
infoLabel.text = text
infoLabel.layer.removeAllAnimations()
}
}
Nothing seems to work unless I use:
CATransaction.begin()
CATransaction.setDisableActions(true)
cell.infoLabel.text = "Changing text here"
CATransaction.commit()
But then I'd have to use it everywhere I'm changing the text. Is there a place I've overlooked to do this more elegantly?

Resources