how to align top the text in this UILabel - swift

Why the justified text won't align top? there's additional padding on top and bottom. I want the text to be aligned top and should only take the required space for the length of the text. How to fit the frame? Deleting height will show only one line.
import SwiftUI
struct TextWidget: View {
private let view = UILabel()
let justified: Bool
var body: some View {
VStack (alignment: .leading) {
HStack (alignment: .top) {
if justified {
JustifiedText("sampletext looonggg texttttsss dddddDDDDD dddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdd ")
.padding(.bottom, 5.0)
}
if !justified {
Text("sampletext looonggg texttttsss dddddDDDDD dddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDDDDDdddddDD dddddDDDDD")
.font(Font.system(size: 25, weight: .regular ))
.padding(.bottom, 5.0)
}
}.background(Color.purple)
} .frame(width: 200, height: 400, alignment: .top)
}
}
struct JustifiedText: UIViewRepresentable {
private let value: String
init(_ string: String) {
value = string
}
func makeUIView(context: Context) -> UILabel {
let view = UILabel()
view.textAlignment = .justified
view.numberOfLines = 0
view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
view.text = value
view.adjustsFontSizeToFitWidth = false
view.allowsDefaultTighteningForTruncation = false
view.font = UIFont.systemFont(ofSize: 20)
return view
}
func updateUIView(_ uiView: UILabel, context: Context) {
}
}
Edit: someone suggested a similar post, but it does not justify the text. justifying text and maintaining font size is a main requirement.

Related

Fading Text ViewModifier to simulate truncationMode .byWordWrapping and apply fading mask

I am trying to create a ViewModifier called .fade that can be used on SwiftUI.Text and simulates a fading effect with a truncation mode of .byWordWrapping known from UIKit. In addition I apply a fading mask to the last line of the Text View.
In my example code for a static View everything works as expected, but for some dynamic changes the modifier creates a wrong layout or does not update properly.
My actual problems are:
changing the lineLimit dynamically
Everything works if going from a larger lineLimit to a smaller one, but the other way going from smaller to larger values is not working
Changing the size of the parent Stack does not resize the view if the view is already faded. Meaning the view exceeds the parent bounds of width: 300 (Can be simulated in the TestView by showing the image while a text is faded.)
Helper ViewModifer to determine the size of a view:
private struct SizePreferenceKey: PreferenceKey {
static var defaultValue: CGSize = .zero
static func reduce(value: inout CGSize, nextValue: () -> CGSize) {}
}
public extension View {
func readSize(onChange: #escaping (CGSize) -> Void) -> some View {
background(
GeometryReader { geometryProxy in
Color.clear
.preference(key: SizePreferenceKey.self, value: geometryProxy.size)
}
)
.onPreferenceChange(SizePreferenceKey.self, perform: onChange)
}
}
The FadingViewModifer itself:
private struct FadingTextViewModifier: ViewModifier {
#State private var intrinsicSize = CGSize.zero
#State private var truncatedSize = CGSize.zero
#State private var isTruncated = false
let lineLimit: Int?
let font: UIFont
let useMaxWidth: Bool
let alignment: Alignment
let isTruncatedAction: ((Bool) -> Void)?
var maxWidth: CGFloat? { useMaxWidth ? .infinity : nil }
func body(content: Content) -> some View {
let truncatedView = content
.font(Font(font))
.lineLimit(lineLimit)
.readSize { size in
truncatedSize = size
isTruncated = truncatedSize != intrinsicSize
isTruncatedAction?(isTruncated)
}
let intrinsicView = content
.font(Font(font))
.fixedSize(horizontal: false, vertical: true)
.readSize { size in
intrinsicSize = size
isTruncated = truncatedSize != intrinsicSize
isTruncatedAction?(isTruncated)
}
let view = content
.font(Font(font))
.frame(maxWidth: maxWidth, alignment: alignment)
.background(truncatedView.hidden(), alignment: alignment)
.background(intrinsicView.hidden(), alignment: alignment)
Group {
if isTruncated {
content
.font(Font(font))
.fixedSize(horizontal: lineLimit == 1, vertical: true)
.frame(width: truncatedSize.width, height: truncatedSize.height, alignment: alignment)
.frame(maxWidth: maxWidth, alignment: alignment)
.clipped()
.mask(maskView)
.background(Color.yellow)
.background(view.hidden())
} else {
view
}
}
.background(Color.purple)
}
}
Extension to create the maskView that is used for the fading:
private extension FadingTextViewModifier {
var ellipsisWidth: CGFloat { lineLimit == 1 ? 0 : 15 }
var fadeOutWidth: CGFloat { 50 }
var maskStart: UnitPoint {
let width = truncatedSize.width
let visibleWidth = width - ellipsisWidth - fadeOutWidth
let startX: CGFloat = width > 0 && visibleWidth > 0 ? visibleWidth / width : 0.8
return .init(x: startX, y: 0.5)
}
var maskEnd: UnitPoint {
let width = truncatedSize.width
let endX: CGFloat = width > 0 && width > ellipsisWidth ? (width - ellipsisWidth) / width : 1
return .init(x: endX, y: 0.5)
}
// Only apllied to the last line.
var maskView: some View {
VStack(spacing: 0) {
Rectangle()
LinearGradient(colors: [.white, .clear], startPoint: maskStart, endPoint: maskEnd)
.frame(height: font.lineHeight)
}
}
}
The actual ViewModifier extension:
extension Text {
/// Fades the `Text` if needed.
/// - Parameters:
/// - lineLimit: Defines the lineLimit of your text. Modifies by default the lineLimit to 1 instead of nil
/// - font: Needs an UIFont to determine the lineHeight for the masking effect. Directly applies the font to the Text.
/// - useMaxWidth: Only relevant for lineLimit of 1. By default the text view uses maxWidth .infinity to avoid a fading effect, although it would look like space is available.
/// - alignment: Specifies the text alignment inside the frame
/// - isTruncatedAction: Returns if the text is truncated or not. Use this to customize your view. e.g. a "show more"-button
func fade(lineLimit: Int? = 1,
font: UIFont = UIFont.systemFont(ofSize: 16),
useMaxWidth: Bool = true,
alignment: Alignment = .topLeading,
isTruncatedAction: ((Bool) -> Void)? = nil) -> some View {
modifier(FadingTextViewModifier(lineLimit: lineLimit,
font: font,
useMaxWidth: useMaxWidth,
alignment: alignment,
isTruncatedAction: isTruncatedAction))
}
}
A testView to simulate the problems:
struct FadingTextTest: View {
#State var withImage = false
#State var useLongText = false
#State var lineLimit = 1
let shortText = "This is a short text!"
let longText = "This is a much much much much much much much much much much longer text!"
var text: String {
useLongText ? longText : shortText
}
var body: some View {
VStack(alignment: .leading, spacing: 20) {
Button("activate image") { withImage.toggle() }
Button("use long text") { useLongText.toggle() }
Stepper("Line Limit \(lineLimit)", value: $lineLimit)
.padding(.bottom, 50)
// Single Fading Text that does not need to adapt to size changes of the parent dynamically
Text(text)
.fade(lineLimit: lineLimit)
// Text that needs to adapt dynamically to width changes inside a Stack
HStack(spacing: 0) {
Text(text)
.fade(lineLimit: lineLimit)
if withImage {
Image(systemName: "star")
.resizable()
.frame(width: 20, height: 20)
}
}
// Text that fades at intrinsic width
Text(text)
.fade(lineLimit: lineLimit, useMaxWidth: false)
// Text without fading, but with lineLimit
Text(text)
.font(Font(UIFont.systemFont(ofSize: 16)))
.lineLimit(lineLimit)
.frame(maxWidth: .infinity, alignment: .topLeading)
// Text without any effect
Text(text)
.font(Font(UIFont.systemFont(ofSize: 16)))
.frame(maxWidth: .infinity, alignment: .topLeading)
Spacer()
}
.frame(maxHeight: .infinity)
.background(Color.green)
.frame(width: 300)
}
}

UITextView Representable in SwiftUI not respecting bounds for width in ScrollView

I want to present text in a UITextView using SwiftUI. I'm using UIViewRepresentable and having issues with the width when it exceeds the bounds of the scrollView. I would like if to wrap the text but instead it just continues on one long line and the left and right are not visible:
This is my code:
struct ContentView: View {
var messages = ["here is some long text to show the textView is not wrapping it's content"]
var body: some View {
GeometryReader { geometry in
VStack(spacing: 0) {
ScrollView(.vertical) {
ScrollViewReader { scrollView in
ZStack {
LazyVStack {
ForEach(messages, id: \.self) { message in
TextView(text: message)
}
}
}
}
}
}
}
}
}
struct TextView: UIViewRepresentable {
var text: String
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.font = UIFont.preferredFont(forTextStyle: UIFont.TextStyle.body)
textView.isScrollEnabled = false
textView.backgroundColor = .clear
textView.isEditable = false
textView.clipsToBounds = true
textView.isEditable = false
textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
textView.backgroundColor = #colorLiteral(red: 0.03137254902, green: 0.4980392157, blue: 0.9960784314, alpha: 1)
textView.textColor = UIColor.white
textView.text = text
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
}
}
I'm wondering if this has something to do with the scrollView. And either way I would like to know if there is a fix to this? I tried setting the frame width to geometry.size.width but that did nothing.
This happens because you're using a UIRepresentable view. There are a few solutions, simplest of them is to use explicit .frame() on top of the UIRepresentable view that is in a SwiftUI view.
e.g. .frame(width: UIScreen.main.bounds.width) so it only extends as much as screen's width.

How to Highlight and Clickable if Text is URL SwiftUI

Is there any way to Highlight the Text And clickable if Text contain URL in the chat screen from both
Sender USer Code:-
import SwiftUI
struct TextHighlightURL: View {
var body: some View {
HStack(alignment: .bottom){
Spacer()
Text("2:22 PM").font(.system(size: 10))
.foregroundColor(.gray)
HStack {
Text("www.google.com")
.foregroundColor(.white)
.multilineTextAlignment(.trailing)
.padding(10)
}
.background(Color.init("DTR-ChatSendrColor"))
.cornerRadius(10.0)
}.padding(.vertical,5)
.padding()
}
}
Output:-
Reciver User Code:-
struct SenderReciverUI1: View {
var body: some View {
Group {
HStack(alignment: .bottom){
VStack(alignment: .leading,spacing:5) {
HStack(alignment: .bottom) {
Text("www.google.com")
.foregroundColor(.white)
.padding(10)
.cornerRadius(10.0)
}
}
Spacer()
}.padding(.vertical,5)
.padding()
}
}
}
My Goal:-
Can someone please explain to me how to show Highlight the Text from both side if sender send the link and receiver receive the link it automatically highlighted And clickable if Text contain URL, I've tried to implement by above but no results yet.
Any help would be greatly appreciated.
Thanks in advance.
You need to create custom UIViewRepresentable for TextView.
check below code this might help you.
struct TextView: UIViewRepresentable {
#Binding var text: String
#Binding var textStyle: UIFont.TextStyle
func makeUIView(context: Context) -> UITextView {
let textView = UITextView()
textView.delegate = context.coordinator
textView.font = UIFont.preferredFont(forTextStyle: textStyle)
textView.autocapitalizationType = .sentences
textView.isSelectable = true
textView.isUserInteractionEnabled = true
textView.isEditable = false
textView.dataDetectorTypes = .link
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
uiView.text = text
uiView.font = UIFont.preferredFont(forTextStyle: textStyle)
}
func makeCoordinator() -> Coordinator {
Coordinator($text)
}
class Coordinator: NSObject, UITextViewDelegate {
var text: Binding<String>
init(_ text: Binding<String>) {
self.text = text
}
func textViewDidChange(_ textView: UITextView) {
self.text.wrappedValue = textView.text
}
}
}
In your "Receiver User Code:-" same for "Sender User Code"
struct SenderReciverUI1: View {
#State private var message = "Hello, www.google.com. this is just testing for hyperlinks, check this out our website https://www.apple.in thank you."
#State private var textStyle = UIFont.TextStyle.body
var body: some View {
Group {
HStack(alignment: .bottom){
VStack(alignment: .leading,spacing:5) {
HStack(alignment: .bottom) {
TextView(text: $message, textStyle: $textStyle)
.foregroundColor(.white)
.padding(10)
.cornerRadius(10.0)
}
}
Spacer()
}.padding(.vertical,5)
.padding()
}
}
}
let me know if you need anything.

SwiftUI Is it possible to disable the gesture on Text of UITextView implemented by UIViewRepresentable?

I made the UITextView by UIViewRepresentable and added TapGesture like below.
But I want to disable the gesture only on text. Just want to activate tap() function on out of text. Is it possible to do that ? I don't know how to detect the position of texts on UIViewRepresentable and disable the gesture...
struct TextView: UIViewRepresentable {
#Binding var text: String
func makeUIView(context: UIViewRepresentableContext<TextView>) -> UITextView {
let textView = UITextView()
textView.backgroundColor = UIColor.clear
textView.isScrollEnabled = false
textView.text = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
textView.font = UIFont(name: "ArialMT", size: 20)
textView.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
return textView
}
func updateUIView(_ uiView: UITextView, context: Context) {
}
}
struct TmpView9: View {
#State var text: String
func tap() -> some Gesture {
TapGesture()
.onEnded{
print("taped!!!!")
}
}
var body: some View {
VStack {
TextView(text: $text)
.frame(width:300, height: 300, alignment: .topLeading)
.border(Color.red, width: 1)
.gesture(tap())
}
}
}

Show line / separator view in SwiftUI

I want to show a separator line in my SwiftUI app. To achieve that, I tried to create an empty view with a fixed frame and a background color / border:
EmptyView()
.frame(width: 200, height: 2)
.background(Color.black) // or:
.border(Color.black, width: 2)
Unfortunately, I cannot see any dark view showing up.
Is there a way to show a separator / line view?
Use Divider:
A visual element that can be used to separate other content.
Example:
struct ContentView : View {
var body: some View {
VStack {
Text("Hello World")
Divider()
Text("Hello Another World")
}
}
}
Output:
If anyone is interested a divider, text, divider, looking like this:
LabelledDivider code
struct LabelledDivider: View {
let label: String
let horizontalPadding: CGFloat
let color: Color
init(label: String, horizontalPadding: CGFloat = 20, color: Color = .gray) {
self.label = label
self.horizontalPadding = horizontalPadding
self.color = color
}
var body: some View {
HStack {
line
Text(label).foregroundColor(color)
line
}
}
var line: some View {
VStack { Divider().background(color) }.padding(horizontalPadding)
}
}
It's kind of ugly but I had to put the Dividers into a VStack to make them horizontal, otherwise, they will be vertical, due to HStack. Please let me know if you managed to simplify this :)
Also maybe using and stored properties for LabelledDivider might not be the most SwiftUI-y solution, so I'm open to improvements.
Example usage
This is the code that results in the screenshot seen above:
struct GetStartedView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SignInView()) {
Text("Sign In").buttonStyleEmerald()
}
LabelledDivider(label: "or")
NavigationLink(destination: SignUpView()) {
Text("Sign up").buttonStyleSaphire()
}
}.padding(20)
}
}
}
ButtonStyle
For sake of completness, I also include buttonStyle view modifiers:
struct ButtonStyle: ViewModifier {
private let color: Color
private let enabled: () -> Bool
init(color: Color, enabled: #escaping () -> Bool = { true }) {
self.color = color
self.enabled = enabled
}
dynamic func body(content: Content) -> some View {
content
.padding()
.frame(minWidth: 0, maxWidth: .infinity, alignment: .center)
.foregroundColor(Color.white)
.background(enabled() ? color : Color.black)
.cornerRadius(5)
}
}
extension View {
dynamic func buttonStyleEmerald(enabled: #escaping () -> Bool = { true }) -> some View {
ModifiedContent(content: self, modifier: ButtonStyle(color: Color.emerald, enabled: enabled))
}
dynamic func buttonStyleSaphire(enabled: #escaping () -> Bool = { true }) -> some View {
ModifiedContent(content: self, modifier: ButtonStyle(color: Color.saphire, enabled: enabled))
}
}
Edit: Please note that Color.saphire and Color.emerald are custom declared colors:
extension Color {
static var emerald: Color { .rgb(036, 180, 126) }
static var forest: Color { .rgb(062, 207, 142) }
}
extension Color {
static func rgb(_ red: UInt8, _ green: UInt8, _ blue: UInt8) -> Color {
func value(_ raw: UInt8) -> Double {
return Double(raw)/Double(255)
}
return Color(
red: value(red),
green: value(green),
blue: value(blue)
)
}
}
You can just draw a line by using Color. If you want to change the line width or padding, you can use frame or padding like other SwiftUI Components.
//Horizontal Line in VStack
VStack{
Color.gray.frame(height: 1 / UIScreen.main.scale)
}
//Vertical Line in HStack
HStack{
Color.gray.frame(width: 1 / UIScreen.main.scale)
}
If you are looking for a way to customize the divider, there isn't any. You must provide your custom implementation:
struct CustomDivider: View {
let height: CGFloat = 1
let color: Color = .white
let opacity: Double = 0.2
var body: some View {
Group {
Rectangle()
}
.frame(height: height)
.foregroundColor(color)
.opacity(opacity)
}
}
HStack {
VStack {
Divider()
}
Text("or")
.font(.caption)
.foregroundColor(Color(UIColor.systemGray))
VStack {
Divider()
}
}