SwiftUI Breaking line in UITextView wrapped by UIViewRepresentable - swift

I'm struggling for breaking line in CustomUITextView. Please help...
I made CustomUITextView like below.
struct CustomTextView: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<CustomTextView>) -> UITextView {
let textView = UITextView()
textView.backgroundColor = UIColor.clear
textView.isScrollEnabled = false
textView.textColor = UIColor.black
textView.font = UIFont(name: "ArialMT", size: 20)
// textView.text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) { }
}
Then using it like this.
struct ContentView: View {
var body: some View {
ZStack {
CustomTextView()
.frame(width:300, height: 300, alignment: .topLeading)
.border(Color.red, width: 1)
}
}
}
By keyboard input it works and lines are returned at red border.
But when I take away this comment in CustomUITextView
textView.text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
It will be like this and doesn't break.
Is it possible to break line when I put Strings directly from code like above too ?
Does anyone know the solutions?

You need to decrease compression resistance priority
textView.font = UIFont(name: "ArialMT", size: 20)
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
textView.text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
return textView

Related

SwiftUI Line break is Weird [duplicate]

I have a text field like this
Text("Hello, one two three four five six seven eight!")
.frame(width:270)
.border(.blue)
When it renders it decides to put seven and eight on the second line even though there is space for seven on the first line. Worse it decides to indent the truncated top line so it is centred within the frame.
How do I fix this so it wraps the text properly without taking into account the orphan?
Edit: Forgot to mention that I wanted this on macOS. I have tried to port it to the Mac. It does correctly left align the text but it doesn't wrap to the second line. The height of the box does get calculated accordingly though.
Here is my updated code:
struct NonOrphanedText: View
{
var text: String
#State private var height: CGFloat = .zero
var body: some View
{
InternalLabelView(text: text, dynamicHeight: $height)
.frame(maxHeight: height)
}
struct InternalLabelView: NSViewRepresentable
{
var text: String
#Binding var dynamicHeight: CGFloat
func makeNSView(context: Context) -> NSTextField
{
let label = NSTextField()
label.isEditable = false
label.isBezeled = false
label.drawsBackground = false
label.isSelectable = false
label.maximumNumberOfLines = 5
label.usesSingleLineMode = false
label.lineBreakStrategy = .init()
label.lineBreakMode = .byWordWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return label
}
func updateNSView(_ nsView: NSTextField, context: Context)
{
nsView.stringValue = text
DispatchQueue.main.async
{
dynamicHeight = nsView.sizeThatFits(CGSize(width: nsView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
}
}
}
}
We need lineBreakStrategy but it is unavailable for now in SwiftUI, so possible solution is to use UILabel.
Here is a possible solution. Tested with Xcode 13.2 / iOS 15.2
struct LabelView: View {
var text: String
#State private var height: CGFloat = .zero
var body: some View {
InternalLabelView(text: text, dynamicHeight: $height)
.frame(maxHeight: height)
}
struct InternalLabelView: UIViewRepresentable {
var text: String
#Binding var dynamicHeight: CGFloat
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.numberOfLines = 0
label.lineBreakStrategy = .init() // << here !!
label.lineBreakMode = .byWordWrapping
label.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
uiView.text = text
DispatchQueue.main.async {
dynamicHeight = uiView.sizeThatFits(CGSize(width: uiView.bounds.width, height: CGFloat.greatestFiniteMagnitude)).height
}
}
}
}

Why UILabelViewRepresentable does not respect to given frame?

I have a simple UILabelViewRepresentable and I gave a frame to it! But after using in SwiftUI it became max size! How could I solve the issue from UIKit code part?
struct ContentView: View {
var body: some View {
UILabelViewRepresentable(configuration: { label in
label.text = "Hello, World!"
label.textAlignment = .center
label.backgroundColor = .blue
label.frame = CGRect(x: 0, y: 0, width: 200, height: 50)
})
}
}
struct UILabelViewRepresentable: UIViewRepresentable {
let configuration: (UILabel) -> ()
func makeUIView(context: Context) -> UILabel {
return UILabel()
}
func updateUIView(_ uiView: UILabel, context: Context) {
configuration(uiView)
}
}
The main issue is that this representable will try constrain the UILabel to fill the whole screen. You can avoid this by making a UIView, and making the UILabel a child of that so you can center it in the parent.
Code:
struct UILabelViewRepresentable: UIViewRepresentable {
let configuration: (UILabel) -> ()
func makeUIView(context: Context) -> UIView {
let view = UIView()
view.addSubview(UILabel())
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
let label = uiView.subviews.first! as! UILabel
label.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: uiView.centerXAnchor),
label.centerYAnchor.constraint(equalTo: uiView.centerYAnchor)
])
configuration(label)
}
}
Usage:
struct ContentView: View {
var body: some View {
UILabelViewRepresentable(configuration: { label in
label.text = "Hello, World!"
label.textAlignment = .center
label.backgroundColor = .blue
label.sizeToFit()
})
}
}
Result:
Depending on your usage, the label.sizeToFit() can be moved inside the UILabelViewRepresentable.
struct ContentView: View {
var body: some View {
UILabelViewRepresentable(text: "Hello, World!",
center: .center,
frame: CGRect(x: 0,
y: 0,
width: 200,
height: 50),
background: .blue)
}
}
struct UILabelViewRepresentable: UIViewRepresentable {
let text : String
let center: NSTextAlignment
let frame: CGRect
let background: UIColor
func makeUIView(context: Context) -> UIView {
let view = UIView()
let label = UILabel()
label.text = text
label.textAlignment = center
label.backgroundColor = background
label.frame = frame
view.addSubview(label)
return view
}
func updateUIView(_ uiView: UIView, context: Context) {
}
}

TextAlignmentStyle .natural doesn't work with SwiftUI

I need my text to change depend on text language not depend on App language
if it's RTL language like Arabic the text alignment should be RTL
if it's LTR language like English the text alignment should be LTR
I tried to import UILabel from UIKit because SwiftUI doesn't have .natural and .justified
like this
struct LabelAlignment: UIViewRepresentable {
var text: String
var textAlignmentStyle : TextAlignmentStyle
var width: CGFloat
var fontSize: CGFloat
var textColorName: String
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
label.textAlignment = NSTextAlignment(rawValue: textAlignmentStyle.rawValue)!
label.numberOfLines = 0
label.preferredMaxLayoutWidth = width
label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentHuggingPriority(.required, for: .vertical)
label.textColor = UIColor(named: textColorName)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
uiView.text = text
if let customFont = UIFont(name: "Almarai-Regular", size: fontSize) {
uiView.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)
uiView.adjustsFontForContentSizeCategory = true
} }
}
enum TextAlignmentStyle : Int{
case left = 0 ,center = 1 , right = 2 ,justified = 3 ,natural = 4
}
then I use it like this
HStack {
LabelAlignment(text: "My text" , textAlignmentStyle: . natural, width:UIScreen.main.bounds.width - 30, fontSize: 15, textColorName: "color3")
.padding(.horizontal,5)
.layoutPriority(1.0)
.foregroundColor(.textColor3)
.regularStyle(size: 15)
.padding([.bottom,.top], 8)
}
it working great with left ,center , right and justified
but it won't work with natural
As Adam mention
if you follow my old answer it won't work correctly if you change the App language
The correct way is
struct LabelAlignment: UIViewRepresentable {
var text: String
var width: CGFloat
var fontSize: CGFloat
var textColorName: String
var detectLangauge = ""
func makeUIView(context: Context) -> UILabel {
let label = UILabel()
if detectLangauge == "Arabic" || detectLangauge == "العربية" {
label.textAlignment = .right
}else {
label.textAlignment = .left
}
label.numberOfLines = 0
label.preferredMaxLayoutWidth = width
label.setContentHuggingPriority(.required, for: .horizontal)
label.setContentHuggingPriority(.required, for: .vertical)
label.textColor = UIColor(named: textColorName)
return label
}
func updateUIView(_ uiView: UILabel, context: Context) {
uiView.text = text
if let customFont = UIFont(name: "Almarai-Regular", size: fontSize) {
uiView.font = UIFontMetrics(forTextStyle: .headline).scaledFont(for: customFont)
uiView.adjustsFontForContentSizeCategory = true
} }
}
Then use it like this
LabelAlignment(text: "My text", width: UIScreen.main.bounds.width - 30, fontSize: 15, textColorName: "color3",detectLangauge:detectLangauge)
it will work as .natural
Old answer :
I found a solution with pure SwiftUI
First you need to add these code
import Foundation
import NaturalLanguage
extension String {
func detectedLanguage() -> String? {
let recognizer = NLLanguageRecognizer()
recognizer.processString(self)
guard let languageCode = recognizer.dominantLanguage?.rawValue else { return nil }
let detectedLanguage = Locale.current.localizedString(forIdentifier: languageCode)
return detectedLanguage
}
}
then you need a #State value to save the result of detectedLanguage()
#State private var detectLangauge = ""
after that check the result of text by using
detectLangauge = "your text".detectedLanguage()
you can check your text language onApper() or after get result from API
then you need to add
Text("Your Text")
.multilineTextAlignment(detectLangauge == "Arabic" ? .trailing : .leading)

How do I create a Multi Line Text Field in SwiftUI for MacOS?

I am writing my first MacOS app using Swift. I need to create a simple editable text box which allows multiple paragraphs. In HTML, this would be a textarea.
I gather that iOS 14 will include TextEditor, but that’s not now, and I don’t know whether that will be in MacOS anyway.
Many solutions I have seen presume iOS and UIKit.
How do I do this with SwiftUI and MacOS?
Update
Someone has suggested that the question is similar to this one: How do I create a multiline TextField in SwiftUI?
I have already looked at that question, but:
The linked question is specifically for iOS, but mine is for MacOS
The answers, as far as I can tell, are specifically for iOS using UIKit. If someone cares to explain how this answers my MacOS question I would be very interested …
Here is some initial demo of component like iOS14 TextEditor.
Demo prepared & tested with Xcode 11.7 / macOS 10.15.6
struct TestTextArea: View {
#State private var text = "Placeholder: Enter some text"
var body: some View {
VStack {
TextArea(text: $text)
.border(Color.black)
// Text(text) // uncomment to see mirror of enterred text
}.padding()
}
}
struct TextArea: NSViewRepresentable {
#Binding var text: String
func makeNSView(context: Context) -> NSScrollView {
context.coordinator.createTextViewStack()
}
func updateNSView(_ nsView: NSScrollView, context: Context) {
if let textArea = nsView.documentView as? NSTextView, textArea.string != self.text {
textArea.string = self.text
}
}
func makeCoordinator() -> Coordinator {
Coordinator(text: $text)
}
class Coordinator: NSObject, NSTextViewDelegate {
var text: Binding<String>
init(text: Binding<String>) {
self.text = text
}
func textView(_ textView: NSTextView, shouldChangeTextIn range: NSRange, replacementString text: String?) -> Bool {
defer {
self.text.wrappedValue = (textView.string as NSString).replacingCharacters(in: range, with: text!)
}
return true
}
fileprivate lazy var textStorage = NSTextStorage()
fileprivate lazy var layoutManager = NSLayoutManager()
fileprivate lazy var textContainer = NSTextContainer()
fileprivate lazy var textView: NSTextView = NSTextView(frame: CGRect(), textContainer: textContainer)
fileprivate lazy var scrollview = NSScrollView()
func createTextViewStack() -> NSScrollView {
let contentSize = scrollview.contentSize
textContainer.containerSize = CGSize(width: contentSize.width, height: CGFloat.greatestFiniteMagnitude)
textContainer.widthTracksTextView = true
textView.minSize = CGSize(width: 0, height: 0)
textView.maxSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
textView.isVerticallyResizable = true
textView.frame = CGRect(x: 0, y: 0, width: contentSize.width, height: contentSize.height)
textView.autoresizingMask = [.width]
textView.delegate = self
scrollview.borderType = .noBorder
scrollview.hasVerticalScroller = true
scrollview.documentView = textView
textStorage.addLayoutManager(layoutManager)
layoutManager.addTextContainer(textContainer)
return scrollview
}
}
}

Swift - TextView that expands and stays in center

I am trying to accomplish an Idea I got in my head but I got stuck..
I need a TextView that expands in both ways: widht-height.
That has a minimum and maximum width, and minimum height.
That is centered in the middle of the parent (SCROLL) view.
And that has a button send at the bottom trailing part of the view.
Here's the idea:
So if the user types in the box then it expands in both directions. But there's a maximum width for it (so it doesn't go offscreen) but the height is not limited: due to the parent scrollview.
The problem is that the textView's height doesn't expand when text is breaking into new line.
Code:
func textViewDidChange(_ textView: UITextView) {
self.adjustTextViewFrames(textView: textView)
}
func adjustTextViewFrames(textView : UITextView){
var newSize = textView.sizeThatFits(CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude))
if newSize.width > self.view.bounds.width - 20 {
newSize.width = self.view.bounds.width - (self.view.bounds.width/10)
}
messageBubbleTextViewWidthConstraint.constant = newSize.width
messageBubbleTextViewHeightConstraint.constant = newSize.height
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
Try This :
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
}
}
}
}
Credit goes to Brian Voong from Let's Build That App here link