Swift: Scroll a UITextView horizontally - ios

I want to display selected contacts on a label which could be scrolled like in Snapchat. After going through lot of questions on stackoverflow I have used Textview since it is scrollable.
#IBOutlet weak var selectedContactsDisplay: UITextView!
selectedContactsDisplay.delegate = self
selectedContactsDisplay.backgroundColor = UIColor.appColor()
selectedContactsDisplay.textColor = UIColor.white
selectedContactsDisplay.textContainer.maximumNumberOfLines = 1
selectedContactsDisplay.textContainer.lineBreakMode = NSLineBreakMode.byTruncatingHead
let stringOne = selectedContactName.joined(separator: ",")
selectedContactsDisplay.text = stringOne
func textViewDidBeginEditing(_ textView: UITextView) {
textView.resignFirstResponder()
}
But, the horizontal scrolling is still not possible. Can someone help me on how can the enable the scrolling.

You can not scroll in a TextView by yourself, what you can do is to enable autoScroll:
#IBOutlet weak var selectedContactsDisplay: UITextView!
selectedContactsDisplay.delegate = self
selectedContactsDisplay.backgroundColor = UIColor.appColor()
selectedContactsDisplay.textColor = UIColor.white
selectedContactsDisplay.textContainer.maximumNumberOfLines = 1
selectedContactsDisplay.textContainer.lineBreakMode = NSLineBreakMode.byTruncatingHead
let stringOne = selectedContactName.joined(separator: ",")
selectedContactsDisplay.text = stringOne
func textViewDidBeginEditing(_ textView: UITextView) {
textView.resignFirstResponder()
let range = NSMakeRange(selectedContactsDisplay.text.characters.count - 1, 0)
selectedContactsDisplay.scrollRangeToVisible(range)
}

You can not scroll horizontally in UITextView. For solution you can take a UIScrollView which can scroll horizontally and can add label or textfield in to it and increase width of that label according to your content! Proper constraint should be set!

Related

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)
}
}

UITextView's textContainer renders at the wrong frame when the height is changed

All I want is to resize the UITextView whenever you type/paste on it. It should be scrollable because I don't want the UITextView to fill up the screen. It could be easily achievable with these few lines of codes.
#IBOutlet weak var mainTextView: UITextView!
#IBOutlet weak var mainTextViewHeightConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.mainTextView.delegate = self
}
// This method is used for calculating the string's height in a UITextField
func heightOf(text: String, for textView: UITextView) -> CGFloat {
let nsstring: NSString = text as NSString
let boundingSize = nsstring.boundingRect(
with: CGSize(width: textView.frame.size.width, height: .greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: textView.font!],
context: nil).size
return ceil(boundingSize.height + textView.textContainerInset.top + textView.textContainerInset.bottom)
}
// This method calculates the UITextView's new height
func calculateHeightFor(text: String, in textView: UITextView) -> CGFloat {
let newString: String = textView.text ?? ""
let minHeight = self.heightOf(text: "", for: textView) // 1 line height
let maxHeight = self.heightOf(text: "\n\n\n\n\n", for: textView) // 6 lines height
let contentHeight = self.heightOf(text: newString, for: textView) // height of the new string
return min(max(minHeight, contentHeight), maxHeight)
}
// The lines inside will change the UITextView's height
func textViewDidChange(_ textView: UITextView) {
self.mainTextViewHeightConstraint.constant = calculateHeightFor(
text: textView.text ?? "",
in: textView)
}
This works perfectly well, except when the you paste a multi-line text, or when you press enter (line break).
To solve the pasting issue, various SO questions suggested that I either create a subclass of UITextView to override the paste(_:) method or I use the textView(_: shouldChangeTextIn....) delegate method. After a few experiments with both the best answer was to use the shouldChangeTextIn method which resulted my code into this
// I replaced `textViewDidChange(_ textView: UITextView)` with this
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
self.mainTextViewHeightConstraint.constant = calculateHeightFor(
text: ((textView.text ?? "") as NSString).replacingCharacters(in: range, with: text),
in: textView)
return true
}
Now what should happen is that when the you paste text the UITextView should look like this. (The pasted string is "A\nA\nA\nA\nA" or 5 lines of just A's)
However it becomes like this
As you can see from the screenshots, the textContainer's frame is at the wrong position. Now the weird thing about this is that it seems to only happen when you're pasting at an empty UITextView.
I've tried setting and resetting the UITextView's frame.size, I've tried manually setting the NSTextContainer's size, and some other hacky solutions but I just can't seem to fix it. How can I solve this?
Additional Information:
Upon looking at the view debugger, here's how it looks like when you type enter (line break)
As we can see, the text container is at the wrong location.
After all this adding a UIView.animate block seems to fix some of the issues but not all of it. Why does this work?
// These snippet kind of fixes the problem
self.mainTextViewHeightConstraint.constant = calculateHeightFor(
text: textView.text ?? "",
in: textView)
UIView.animate(withDuration: 0.1) {
self.view.layoutIfNeeded()
}
Note: I didn't place the UIView.animate solution because it doesn't cover all the bugs.
Note:
OS Support: iOS 8.xx - iOS 12.xx
Xcode: v10.10
Swift: 3 and 4 (I tried on both)
PS: yes I have already been on other SO questions such ones listed on the bottom and a few others
TextContainer isn't resizing while changing bounds of UITextView
UITextView's text going beyond bounds
UITextView stange animation glitch on paste action (iOS11)
How to calculate TextView height base on text
You can ignore all the code for calculating height and use the set the textview attribute scrolling Enabled = false
the textview will adjust it self to fit with the text content
image1
then change the code as you want
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
self.textView.text = "A\nA\nA\nA\nA"
}
The result will be like this
image2

Dynamic UITextView mislocation behavior

I am trying to have a textview similar to iPhone messages, where the textview initially has a constraint (height <= 100) and the scrollEnabled = false
This is a link to the project:
https://github.com/akawther/TextView
The text view increases in height based on the content size as in the image on the left until it reaches the height of 100, then the scrollEnabled is set to true. It works perfectly until I click the "send" button on the lower right where the textView should become empty and go back to the original height and scrollEnabled becomes false. The middle image shows what happens when I click the button. When I start typing the textview moves down as you see in the last image on the right.
I want to be able to click the button and eliminate the behavior shown on the middle image, how can I fix this?
import UIKit
class ViewController: UIViewController, UITextViewDelegate {
#IBOutlet weak var bottomConstraint: NSLayoutConstraint!
#IBOutlet weak var messageTextView: UITextView!
#IBOutlet weak var parent: UIView!
let messageTextViewMaxHeight: CGFloat = 100
override func viewDidLoad() {
super.viewDidLoad()
self.messageTextView.delegate = self
}
#IBAction func Reset(sender: AnyObject) {
messageTextView.text = ""
messageTextView.frame.size.height = messageTextView.contentSize.height
messageTextView.scrollEnabled = false
self.parent.layoutIfNeeded()
}
func textViewDidChange(textView: UITextView) {
if textView.frame.size.height >= self.messageTextViewMaxHeight {
textView.scrollEnabled = true
} else {
textView.scrollEnabled = false
textView.frame.size.height = textView.contentSize.height
}
}
}
You can replicate my issue by following these steps in the github project:
1. keep typing words and pressing enters until you start seeing the scroll
2. Click the button you will see that the textview goes up in the blue
container. This is the issue I want to eliminate!
Try bellow code :-
#IBAction func Reset(sender: AnyObject) {
messageTextView.scrollEnabled = false
messageTextView.text = ""
messageTextView.frame.size.height = messageTextView.contentSize.height
parent.frame.size.height = 20
self.view.layoutIfNeeded()
}
func textViewDidChange(textView: UITextView) {
if textView.contentSize.height >= self.messageTextViewMaxHeight {
textView.scrollEnabled = true
} else {
textView.frame.size.height = textView.contentSize.height
textView.scrollEnabled = false
}
}
Your issue is that the UITextView has conflicting properties:
Place on the screen
Size
The size being constrained will cause an issue when you need a resizable TextView. Also, when the TextView is resized, its location is being changed in this case.
Alternate method to approach the issue:
Try setting constraints to its location in relation to the bottom of the screen. When the Keyboard appears, you should move the TextView up with it. Also setting constraints on the height of a resizable TextView is bad practice unless you are planning on forcing the user to scroll.
Hope this helps.
If you are using auto layout, you should be updating to constraint instead of updating the textView.frame.Try create a IBOutlet for your textView heightConstraint then set the updated height to it.
IBOutlet weak var textViewHeightConstraint: NSLayoutConstraint!
//calculate the height and update the constant
textViewHeightConstraint.constant = textView.contentSize.height

UIScrollView needs to expand based upon UILabel sizes but maintaining bottom tab bar

So I have a self made menu bar at the bottom of my ViewController that I need to keep constrained to the bottom of the screen. Between the navigation bar and the bottom bar I want a scroll view so I inserted a UIScrollView and within that scroll view I put a UIView. I did this because I was getting an error that was addressed with this thread:
UIScrollView Scrollable Content Size Ambiguity
So I have some labels that adjust in size based upon the text being used for the labels. I want the UIScrollView and also the associated UIView to adjust based upon how long the UILabels are. Right now I have it where the labels go below the bottom bar but the view isn't scrolling. Here is my code:
//
// ItemViewController.swift
//
import UIKit
import Parse
class ItemViewController: UIViewController {
#IBOutlet weak var menuButton: UIButton!
#IBOutlet weak var mainImage: UIImageView!
#IBOutlet weak var innerView: UIView!
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var heading1: UILabel!
#IBOutlet weak var body1: UILabel!
#IBOutlet weak var heading2: UILabel!
#IBOutlet weak var body2: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
for var y = 0; y < detailsHeadings.count; y++ {
switch y {
case 0:
heading1.text = detailsHeadings[y]
body1.text = details[heading1.text!]
let numLines = calcLines(body1.text!)
body1.numberOfLines = numLines!
break;
case 1:
heading2.text = detailsHeadings[y]
body2.text = details[heading2.text!]
let numLines = calcLines(body2.text!)
body2.numberOfLines = numLines!
break;
default:
break;
}
}
//none of the below worked
scrollView.sizeToFit()
scrollView.sizeThatFits(CGSize(width: 310, height: 700))
innerView.sizeThatFits(CGSize(width: 310, height: 700))
innerView.frame = CGRect(x: 0, y: 0, width: 320, height: 700)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func calcLines(textString: String) -> Int? {
let text = textString
// cast text to NSString so we can use sizeWithAttributes
var myText = text as NSString
//Set attributes
var attributes = [NSFontAttributeName : UIFont.systemFontOfSize(12)]
//Calculate the size of your UILabel by using the systemfont and the paragraph we created before. Edit the font and replace it with yours if you use another
var labelSize = myText.boundingRectWithSize(CGSizeMake(body1.bounds.width, CGFloat.max), options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: attributes, context: nil)
//Now we return the amount of lines using the ceil method
var lines = ceil(CGFloat(labelSize.height) / body1.font.lineHeight)
println(lines)
let linesInt: Int = Int(lines)
return linesInt
}
}
Does anyone have any idea how to achieve this? Any help would be greatly appreciated.
Try these steps:
Set all leading, trailing, top, and bottom constraints of your label to its superview and the superview's to the scrollView
Set your label's number of lines to 0 (unlimited)
Assuming that you're using autolayout since you're referring to UIScrollView content size ambiguity

Dynamically adjust the height of the UITextView depending on its content? [duplicate]

This question already has answers here:
How do I size a UITextView to its content?
(41 answers)
Closed 7 years ago.
I have an UITextView that loads different text depending on the route the user has taken in order to get to the view with the text on it.
How do I dynamically adjust the height of the UITextView depending on its content using Swift?
Setup your constraints so that the edges are pinned but allow the text view to grow vertically. Then set a height constraint (the value doesn't matter here). Create an #IBOutlet for the UITextView and the height constraint. Then we can dynamically change the height in code:
class ViewControler: UIViewController, UITextViewDelegate {
#IBOutlet weak var textView: UITextView!
#IBOutlet weak var textViewHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
self.textView.delegate = self
}
func textViewDidChange(textView: UITextView) {
let sizeToFitIn = CGSizeMake(self.textView.bounds.size.width, CGFloat(MAXFLOAT))
let newSize = self.textView.sizeThatFits(sizeToFitIn)
self.textViewHeight.constant = newSize.height
}
}

Resources