UITextview write in Text Center Alignment , swift - ios

I have one Text Editor when I tap to Bold Button
I can write in Bold here is code
txtEditor.typingAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.black/, NSAttributedStringKey.font.rawValue: UIFont.boldSystemFont(ofSize: (txtEditor.font?.pointSize)!)]
I want to have one button when I tap to Button (The Line who I write) I need to in Centre Text Center Alignment and not other Line before
here is code
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center
but after how will to add this 2 line code in UITextview for typing in Centre Alignment ?

Try this.
extension UITextView {
func centerText() {
self.textAlignment = .center
let fittingSize = CGSize(width: bounds.width, height: CGFloat.greatestFiniteMagnitude)
let size = sizeThatFits(fittingSize)
let topOffset = (bounds.size.height - size.height * zoomScale) / 2
let positiveTopOffset = max(1, topOffset)
contentOffset.y = -positiveTopOffset
}
}
use like this
#IBAction func btnClick(_ sender: Any) {
textView.centerText()
}

Swift 4+
func textViewDidChange(_ textView: UITextView) { textView.textAlignment = .center }

Related

UITextView does not adjust size when used in SwiftUI

My ultimate goal is to display html content in SwiftUI.
For that I am using UIKit's UITextView (I can't use web view, because I need to control font and text color).
This is the entire code of the view representable:
struct HTMLTextView: UIViewRepresentable {
private var htmlString: String
private var maxWidth: CGFloat = 0
private var font: UIFont = .systemFont(ofSize: 14)
private var textColor: UIColor = .darkText
init(htmlString: String) {
self.htmlString = htmlString
}
func makeUIView(context: UIViewRepresentableContext<HTMLTextView>) -> UITextView {
let textView = UITextView()
textView.isScrollEnabled = false
textView.isEditable = false
textView.backgroundColor = .clear
update(textView: textView)
return textView
}
func updateUIView(_ textView: UITextView, context: UIViewRepresentableContext<HTMLTextView>) {
update(textView: textView)
}
func sizeToFit(width: CGFloat) -> Self {
var textView = self
textView.maxWidth = width
return textView
}
func font(_ font: UIFont) -> Self {
var textView = self
textView.font = font
return textView
}
func textColor(_ textColor: UIColor) -> Self {
var textView = self
textView.textColor = textColor
return textView
}
// MARK: - Private
private func update(textView: UITextView) {
textView.attributedText = buildAttributedString(fromHTML: htmlString)
// this is one of the options that don't work
let size = textView.sizeThatFits(CGSize(width: width, height: .greatestFiniteMagnitude))
textView.frame.size = size
}
private func buildAttributedString(fromHTML htmlString: String) -> NSAttributedString {
let htmlData = Data(htmlString.utf8)
let options = [NSAttributedString.DocumentReadingOptionKey.documentType: NSAttributedString.DocumentType.html]
let attributedString = try? NSMutableAttributedString(data: htmlData, options: options, documentAttributes: nil)
let range = NSRange(location: 0, length: attributedString?.length ?? 0)
attributedString?.addAttributes([.font: font,
.foregroundColor: textColor],
range: range)
return attributedString ?? NSAttributedString(string: "")
}
}
It is called from the SwiftUI code like this:
HTMLTextView(htmlString: "some string with html tags")
.font(.systemFont(ofSize: 15))
.textColor(descriptionTextColor)
.sizeToFit(width: 200)
The idea is that the HTMLTextView would stick to the width (here 200, but in practice - the screen width) and grow vertically when the text is multiline.
The problem is whatever I do (see below), it is always displayed as a one line of text stretching outside of screen on the left and right. And it never grows vertically.
The stuff I tried:
calculating the size and setting the frame (you can see that in the code snippet)
doing the above + fixedSize() on the SwiftUI side
setting frame(width: ...) on the SwiftUI side
setting translatesAutoresizingMaskIntoConstraints to false
setting hugging priorities to required
setting ideal width on the SwiftUI side
Nothing helped. Any advice on how could I solve this will be very welcome!
P.S. I can't use SwiftUI's AttributedString, because I need to support iOS 14.
UPDATE:
I have removed all the code with maxWidth and calculating size. And added textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal) when creating the textView in makeUIView(context:). This kind of solved the problem, except for this: even though the height of the text view is correct, the last line is not visible; if I rotate to landscape, it becomes visible; rotate to portrait - not visible again.
UPDATE 2:
After some trial and error I figured out that it is ScrollView to blame. HTMLTextView is inside VStack, which is inside ScrollView. When I remove scroll view, everything sizes correctly.
The problem is, I need scrolling when the content is too long.
So, in the end, I had to move calculating the size that the attributed string would take in the text view with the given font/size etc into the view model, and then set .frame(width:, height:) to those values.
Not ideal, as the pre-calculated height seems a little bit larger than the actual text's height, but could not find better solution for now.
Update (for readability):
I calculate the actual size in view model (calculateDescriptionSize(limitedToWidth maxWidth:), and then I use the result on the Swift UI view:
HTMLTextView(htmlString: viewModel.attributedDescription)
.frame(width: maxWidth, height: viewModel.calculateDescriptionSize(limitedToWidth: maxWidth).height)
where HTMLTextView is my custom view wrapping the UIKit text view.
And this is the size calculation:
func calculateDescriptionSize(limitedToWidth maxWidth: CGFloat) -> CGSize {
// source: https://stackoverflow.com/questions/54497598/nsattributedstring-boundingrect-returns-wrong-height
let textStorage = NSTextStorage(attributedString: attributedDescription)
let size = CGSize(width: maxWidth, height: CGFloat.greatestFiniteMagnitude)
let boundingRect = CGRect(origin: .zero, size: size)
let textContainer = NSTextContainer(size: size)
textContainer.lineFragmentPadding = 0
let layoutManager = NSLayoutManager()
layoutManager.addTextContainer(textContainer)
textStorage.addLayoutManager(layoutManager)
layoutManager.glyphRange(forBoundingRect: boundingRect, in: textContainer)
let rect = layoutManager.usedRect(for: textContainer)
return rect.integral.size
}

Auto-size view with dynamic font in enclosed textview

So here's one I just can't seem to find a matching case for in searching on here.
I have a small UIView that contains a UITextView, and the UIView needs to auto-size around the TextView for presentation over another view. Basically the TextView needs to fully fill the UIView, and the UIView should only be big enough to contain the TextView.
The TextView just contains a couple sentences that are meant to stay on the screen until an external thing happens, and certain values change.
Everything is great when I used a fixed-size font.
But hey... I'm an old guy, and I have the text size jacked up a bit on my phone. Testing it on my device shows where I must be missing something.
When using the dynamic font style "Title 2" in the textview properties, and turning on "Automatically adjust font" in the TextView properties, and having the text larger than the default, it seems as if I'm not properly capturing the size of the TextView's growth (with the bigger text) when creating the new bounding rect to toss at the frame. It's returning values that look a lot like the smaller, default-size text values rather than the increased text size.
Code is below, the view's class code as well as the calling code (made super explicit for posting here). I figure I'm either missing something silly like capturing the size after something happens to the fonts, but even moving this code to a new function and explicitly calling it after the controls fully draw doesn't seem to do it.
I hope this make sense.
Thanks, all.
Calling code:
let noWView:NoWitnessesYetView = (Bundle.main.loadNibNamed("NoWitnessesYetView", owner: nil, options: nil)!.first as! NoWitnessesYetView)
//if nil != noWView {
let leftGutter:CGFloat = 20.0
let bottomGutter:CGFloat = 24.0
let newWidth = self.view.frame.width - ( leftGutter + leftGutter )
let newTop = (eventMap.frame.minY + eventMap.frame.height) - ( noWView.frame.height + bottomGutter ) // I suspect here is the issue
// I suspect that loading without drawing is maybe not allowing
// the fonts to properly draw and the
// TextView to figure out the size...?
noWView.frame = CGRect(x: 20, y: newTop, width: newWidth, height: noWView.frame.height)
self.view.addSubview(noWView)
//}
Class code:
import UIKit
class NoWitnessesYetView: UIView {
#IBOutlet weak var textView: EyeneedRoundedTextView!
override func draw(_ rect: CGRect) {
let newWidth = self.frame.width
// form up a dummy size just to get the proper height for the popup
let workingSize:CGSize = self.textView.sizeThatFits(CGSize(width: newWidth, height: CGFloat(MAXFLOAT)))
// then build the real newSize value
let newSize = CGSize(width: newWidth, height: workingSize.height)
textView.frame.size = newSize
self.textView.isHidden = false
}
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear // .blue
self.layer.cornerRadius = 10
}
}
This perfect way to do it the content comes from : https://www.youtube.com/watch?v=0Jb29c22xu8 .
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// let's create our text view
let textView = UITextView()
textView.frame = CGRect(x: 0, y: 0, width: 200, height: 100)
textView.backgroundColor = .lightGray
textView.text = "Here is some default text that we want to show and it might be a couple of lines that are word wrapped"
view.addSubview(textView)
// use auto layout to set my textview frame...kinda
textView.translatesAutoresizingMaskIntoConstraints = false
[
textView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
textView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
textView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
textView.heightAnchor.constraint(equalToConstant: 50)
].forEach{ $0.isActive = true }
textView.font = UIFont.preferredFont(forTextStyle: .headline)
textView.delegate = self
textView.isScrollEnabled = false
textViewDidChange(textView)
}
}
extension ViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
print(textView.text)
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
}

How to get the content Height of dynamically changing Text in textview?

I have a dynamically changing text in TextView.I could not be able to get the content Height of TextView.
Here is what i tried.
let height = self.tvComment.contentSize.height
print("height",height)
let contentSizeComment = self.tvComment.sizeThatFits(self.tvComment.bounds.size)
print("height",contentSizeComment)
Why it's not getting the content height of TextView?
Hope you understand my problem.
Thanks in Advance
Usethis method to get the height -
func heightForString(text:String, font:UIFont, width:CGFloat) -> CGFloat{
let label:UILabel = UILabel(frame: CGRect(x: 0, y: 0, width: width, height: CGFloat.greatestFiniteMagnitude))
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.byWordWrapping
label.font = font
label.text = text
label.sizeToFit()
return label.frame.size.height
}
in textdidchange you can use this code,in order to resize when textchange
//approxi should be the width of your textview
let approxi = view.frame.width - 90
//size is the max width and height of textview,1000 can be what ever you want
let size = CGSize(width: approxi, height: 1000)
//dont forget to put your font and size
//chey is the text of thetext view
let attributes = [NSAttributedStringKey.font: UIFont.systemFont(ofSize: 15)]
let estim = NSString(string: chey).boundingRect(with: size, options: .usesLineFragmentOrigin, attributes: attributes, context: nil)
//estim is height
above it the first method,second method will come in edit
second method is
func pva() {
//what was the name of my textfield
let fixedwidth = what.frame.size.width - 40
let newsize = what.contentSize.height
self.hrightext.constant = newsize
self.view.layoutIfNeeded()
}
note: both are tested and works in swift4
Your just write code in below delegate of UITextView:-
func textViewDidChange(_ textView: UITextView!) {
let height = self.tvComment.contentSize.height
print("height",height)
let contentSizeComment = self.tvComment.sizeThatFits(self.tvComment.bounds.size)
print("height",contentSizeComment)
}
I hope it help you,
Thank you.

How to lock caret y position in UITextView to create typewriter effect?

I'm trying to force UITextView to keep caret always on the same fixed height, for example in the 1/4 of screen.
I should behave similar to old typewriters - when user presses enter (or reaches end of line) text should scroll one line up and caret should stay in the same y position and jump to the begining of new line.
I was trying to do it like so, but it behaves unexpectedly, caret jumps randomly sometimes and scrolling is visible, it scrolls itself down and then I scroll it up again with scrollRectToVisible, this do not seem like ideal way of doing it.
How can I achieve such effect? Any library or pod with similar functionality would also be much appreciated.
func setScrollToMiddle() {
if let selectedRange = textView.selectedTextRange {
let caretRect = textView.caretRect(for: selectedRange.start)
let middleOfCaretHeight = caretRect.origin.y + (caretRect.height / 2)
let screenHeight = UIScreen.main.bounds.height
guard let kbSize = self.keyboardSize else { return }
let keyboardHeight = kbSize.height
let visibleTextAreaHeight = screenHeight - keyboardHeight - topMenuView.frame.height
let finalRectY = middleOfCaretHeight - topMenuView.frame.height - (visibleTextAreaHeight / 2)
let finalRect = CGRect(x: 0.0, y: finalRectY, width: textView.frame.width, height: visibleTextAreaHeight)
textView.scrollRectToVisible(finalRect, animated: false)
}
}
Here is what I would do:
First set up these in viewDid load
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
textView.isEditable = true
textView.isScrollEnabled = false
}
Then add this extension:
extension ViewController: UITextViewDelegate {
func trackCaret(_ textView:UITextView){
if let selectedRange = textView.selectedTextRange {
let caretRect = textView.caretRect(for: selectedRange.start)
let yPos = caretRect.origin.y - (textView.frame.height/2)
let xPos = caretRect.origin.x - (textView.frame.width/2)
textView.bounds.origin = CGPoint(x: xPos, y: yPos)
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
trackCaret(textView)
return true
}
func textViewDidBeginEditing(_ textView: UITextView) {
trackCaret(textView)
}
func textViewDidEndEditing(_ textView: UITextView) {
// If you don't need to move back to the original position you can leave this out
// textView.bounds.origin = CGPoint.zero
//or if you want smooth animated scroll back then
textView.scrollRectToVisible(CGRect(x:0,
y:0,
width: textView.bounds.width,
height: textView.bounds.height),
animated: true)
}
}
With this you get the typewriter effect without jumping all over the place. The didendEditing method is only there to scroll back to origin 0,0. If you don't need to do it just remove it.

UITextView that starts scrolling when text reached N lines

I'm start implementing text input to a chat app and wondering that is standard behavior of a UITextView with scroll enabled absolutely does not meet expectations.
I want just it is done in chats like WhatsApp. When text reached N, 5 for example lines, scroll bar appear and text container starts scrolling. I wrote code like this, but it doesn't work.
As i think needs to count rows in text container and make content insets, or something like this.
func textViewDidChange(_ textView: UITextView) {
let fixedWidth = myTextView.frame.size.width
myTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = myTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = myTextView.frame
let oldFrame = myTextView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
myTextView.frame = newFrame
let shift = oldFrame.height - newFrame.height
textView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom: shift, right: 0)
textView.scrollIndicatorInsets = textView.contentInset
textView.scrollRangeToVisible(textView.selectedRange)
}
And myTextView is specified as:
let myTextView : UITextView = {
let textView = UITextView()
textView.translatesAutoresizingMaskIntoConstraints = false
textView.isScrollEnabled = false
textView.textContainer.maximumNumberOfLines = 5
textView.textContainer.lineBreakMode = .byWordWrapping
textView.inputAccessoryView = UIView()
return textView
}()
Not based on number of lines, but on a user defined height. You'll find your answer here:
https://stackoverflow.com/a/51235517/10115072
If you want this behaviour to happen is simple:
Create a UIView having UITextView inside
Create a height constraint in UIView priority 1000 of less than or equal your MAX_HEIGHT and also greater than or equal you MIN_HEIGHT
Create a height constraint in you TextView priority 999 equal to your MIN_HEIGHT
Then add this code to your controller
Code:
class YourViewController: KUIViewController {
#IBOutlet weak var textView: UITextView!
override func viewDidLoad() {
super.viewDidLoad()
textView.delegate = self
textView.isScrollEnabled = true
}
}
extension YourViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
let size = CGSize(width: view.frame.width, height: .infinity)
let estimatedSize = textView.sizeThatFits(size)
textView.constraints.forEach { (constraint) in
if constraint.firstAttribute == .height {
constraint.constant = estimatedSize.height
}
}
}
}
This has the same behaviour as WhatsApp textView

Resources