Why UILabelViewRepresentable does not respect to given frame? - swift

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

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

SwiftUI spring timing doesn't appear to match UIKit spring timing

With identical parameters, the SwiftUI spring timing function doesn't appear to match the UIKit spring timing function (specifically, UIViewPropertyAnimator).
In the video below, we have two labels, one in SwiftUI and one in UIKit, with positions being driven by spring functions with identical parameters, but the computed positions don't line up throughout the animation. What am I missing here?
Video
Code
import SwiftUI
import UIKit
class SwiftUIViewModel: ObservableObject {
#Published var position: CGPoint = .zero
}
struct SwiftUIView: View {
#ObservedObject var model: SwiftUIViewModel
var body: some View {
GeometryReader { geo in
Text("Hello")
.font(.system(size: 100))
.foregroundColor(.blue)
.position(model.position)
.animation(.interpolatingSpring(mass: 1, stiffness: 39.47841760435743, damping: 12.566370614359172, initialVelocity: .zero))
}
}
}
class UIKitView: UIView {
let referenceViewModel = SwiftUIViewModel()
let referenceView: UIHostingController<SwiftUIView>
let label = UILabel()
var animator: UIViewPropertyAnimator?
let tapGesture = UITapGestureRecognizer()
var previousBounds: CGRect = .zero
override func layoutSubviews() {
guard bounds != previousBounds else {
return
}
previousBounds = bounds
label.sizeToFit()
label.center = .init(x: bounds.width / 2, y: bounds.height / 2)
referenceView.view.frame = bounds
}
init() {
referenceView = UIHostingController(rootView: SwiftUIView(model: referenceViewModel))
super.init(frame: .zero)
tapGesture.addTarget(self, action: #selector(tapped))
addGestureRecognizer(tapGesture)
addSubview(referenceView.view)
label.text = "Hello"
label.textColor = .black
label.adjustsFontSizeToFitWidth = true
label.font = .systemFont(ofSize: 100.0)
label.layer.opacity = 1.0
addSubview(label)
}
#objc func tapped(_ event: UIEvent) {
let location = tapGesture.location(in: self)
// Forward to reference view.
referenceViewModel.position = location
let timing = UISpringTimingParameters(mass: 1, stiffness: 39.47841760435743, damping: 12.566370614359172, initialVelocity: .zero)
animator = UIViewPropertyAnimator(duration: 0.0, timingParameters: timing)
animator.addAnimations { [weak self] in
self?.label.center = location
}
animator.startAnimation()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

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)

Slider with custom thumb image and text

Hy,
I'm trying to customize a slider by changing the thumb image and add a label over the picture.
For this, in my view in I set the image for slider thumb:
class SliderView: NibLoadingView {
#IBOutlet weak var slider: ThumbTextSlider!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
contentView = legacyLoadXib()
setup()
}
override func setup() {
super.setup()
self.slider.setThumbImage(UIImage(named: "onb_cc_slider_thumb")!, for: .normal)
self.slider.thumbTextLabel.font = UIFont(name: Fonts.SanFranciscoDisplayRegular, size: 14)
}
}
In ThumbTextSlider class I set the text label as below:
class ThumbTextSlider: UISlider {
var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = thumbFrame
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.layer.zPosition = layer.zPosition + 1
}
}
When I made test the result was a little different.
How, can I fix the issue ?
The expected result:
Kind regards
This class may help you. In class instead of image I created image you can replace with you thumb image.
class ThumbTextSlider: UISlider {
private var thumbTextLabel: UILabel = UILabel()
private var thumbFrame: CGRect {
return thumbRect(forBounds: bounds, trackRect: trackRect(forBounds: bounds), value: value)
}
private lazy var thumbView: UIView = {
let thumb = UIView()
return thumb
}()
override func layoutSubviews() {
super.layoutSubviews()
thumbTextLabel.frame = CGRect(x: thumbFrame.origin.x, y: thumbFrame.origin.y, width: thumbFrame.size.width, height: thumbFrame.size.height)
self.setValue()
}
private func setValue() {
thumbTextLabel.text = self.value.description
}
override func awakeFromNib() {
super.awakeFromNib()
addSubview(thumbTextLabel)
thumbTextLabel.textAlignment = .center
thumbTextLabel.textColor = .blue
thumbTextLabel.adjustsFontSizeToFitWidth = true
thumbTextLabel.layer.zPosition = layer.zPosition + 1
let thumb = thumbImage()
setThumbImage(thumb, for: .normal)
}
private func thumbImage() -> UIImage {
let width = 100
thumbView.frame = CGRect(x: 0, y: 15, width: width, height: 30)
thumbView.layer.cornerRadius = 15
let renderer = UIGraphicsImageRenderer(bounds: thumbView.bounds)
return renderer.image { rendererContext in
rendererContext.cgContext.setShadow(offset: .zero, blur: 5, color: UIColor.black.cgColor)
thumbView.backgroundColor = .red
thumbView.layer.render(in: rendererContext.cgContext)
}
}
override func trackRect(forBounds bounds: CGRect) -> CGRect {
return CGRect(origin: bounds.origin, size: CGSize(width: bounds.width, height: 5))
}
}

SwiftUI Breaking line in UITextView wrapped by UIViewRepresentable

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