I'm trying to use a component created with UIKit in SwiftUI by using UIViewRepresentable. The result I want to achieve is to have a textfield on top of my view and my UIKit view stacked at the bottom with an automatic height.
The problem is this component includes a multiline label and from what I see it makes it really hard to have an automatic height working properly, so my UIKit component takes all the available space in my VStack.
Expected result
Actual result
Here is my code included in a playground to test it. I tried to play with hugging priorities but nothing worked for me, and if I test with a swift UI view it works correctly.. Any ideas?
import UIKit
import Foundation
import SwiftUI
import PlaygroundSupport
class InformationView: UIView {
lazy var label = UILabel()
lazy var button = UIButton(type: .custom)
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .red
addSubview(label)
addSubview(button)
label.text = "zaeiof azoeinf aozienf oaizenf oazeinf oaziefj oazeijf oaziejf aozijf oaizje foazeafjoj"
label.numberOfLines = 0
label.setContentHuggingPriority(.required, for: .vertical)
label.translatesAutoresizingMaskIntoConstraints = false
button.translatesAutoresizingMaskIntoConstraints = false
button.setTitle("My button title", for: .normal)
button.setContentCompressionResistancePriority(.required, for: .horizontal)
addConstraints([
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.topAnchor.constraint(equalTo: topAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
label.trailingAnchor.constraint(equalTo: button.leadingAnchor, constant: -10),
button.trailingAnchor.constraint(equalTo: trailingAnchor),
button.centerYAnchor.constraint(equalTo: centerYAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
struct InformationRepresenter: UIViewRepresentable {
func makeUIView(context: Context) -> InformationView {
let view = InformationView(frame: .zero)
view.setContentHuggingPriority(.required, for: .vertical)
return view
}
func updateUIView(_ uiView: InformationView, context: Context) {
}
}
struct Info: View {
var body: some View {
return HStack {
Text("zaeiof azoeinf aozienf oaizenf oazeinf oaziefj oazeijf oaziejf aozijf oaizje foazeafjoj")
Button("My button title", action: {
print("test")
})
}
}
}
struct ContentView: View {
#State var text = ""
var body: some View {
VStack {
TextField("Field", text: $text)
.padding()
Spacer()
// SwiftUI works
// Info().background(Color.red).padding()
// UIViewRepresentable doesn't
InformationRepresenter().padding()
}
}
}
PlaygroundPage.current.setLiveView(ContentView().frame(width: 400, height: 800, alignment: .top))
There's a few things going on here.
InformationView doesn't have an intrinsicContentSize. SwiftUI relies on this property to determine a UIView's ideal size.
No fixedSize modifier. This modifier forces a view to maintain its ideal size rather than grow to fill its parent.
You can't add an intrinsicContentSize because the content size is dynamic; it's based on the number of lines in your label.
You can't add the fixedSize modifier, because without an intrinsicContentSize, this will set the size to (0,0).
🐓🥚
One solution is to wrap InformationView in a UIView that measures the InformationView size, and updates its own intrinsicContentSize according to that measurement.
Your InformationView should fill the width of the screen; it doesn't have an intrinsic width. On the other hand, the intrinsic height should be equal to the height of the compressed system layout size. This is "the optimal size of the view based on its constraints".
Here is the wrapper:
class IntrinsicHeightView<ContentView: UIView>: UIView {
var contentView: ContentView
init(contentView: ContentView) {
self.contentView = contentView
super.init(frame: .zero)
backgroundColor = .clear
addSubview(contentView)
}
#available(*, unavailable) required init?(coder _: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private var contentHeight: CGFloat = .zero {
didSet { invalidateIntrinsicContentSize() }
}
override var intrinsicContentSize: CGSize {
.init(width: UIView.noIntrinsicMetric, height: contentHeight)
}
override var frame: CGRect {
didSet {
guard frame != oldValue else { return }
contentView.frame = self.bounds
contentView.layoutIfNeeded()
let targetSize = CGSize(width: frame.width, height: UIView.layoutFittingCompressedSize.height)
contentHeight = contentView.systemLayoutSizeFitting(
targetSize,
withHorizontalFittingPriority: .required,
verticalFittingPriority: .fittingSizeLevel).height
}
}
}
This IntrinsicHeightView is a generic UIView that, when its frame changes, recalculates the height of its intrinsicContentSize according to the compressed system layout height of its content view.
Here is the update to InformationRepresenter:
struct InformationRepresenter: UIViewRepresentable {
func makeUIView(context: Context) -> IntrinsicHeightView<InformationView> {
return IntrinsicHeightView(contentView: InformationView())
}
func updateUIView(_ uiView: IntrinsicHeightView<InformationView>, context: Context) {
}
}
And finally, when using it in SwiftUI:
InformationRepresenter()
.padding()
.fixedSize(horizontal: false, vertical: true)
Using the fixedSize modifier this way allows the UIView to fill the parent, but use intrinsicContentSize.height as its height. Here is the final result. Good luck!
Related
I have subclass of UIButton, which defines it's height inside by using NSLayoutConstraints, which I need to reuse in SwiftUI view by wrapping it into UIViewRepresentable.
So here is the code:
struct TestView: View {
var body: some View {
TestButtonWrapper()
.background(Color.red)
}
}
final class TestButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
translatesAutoresizingMaskIntoConstraints = false
setTitle("Hello", for: .normal)
// these are ignored:
heightAnchor.constraint(equalToConstant: 200).isActive = true
widthAnchor.constraint(equalToConstant: 300).isActive = true
}
}
struct TestButtonWrapper: UIViewRepresentable {
func makeUIView(context: Context) -> TestButton {
let view = TestButton()
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
return view
}
func updateUIView(_ uiView: TestButton, context: Context) {
}
}
Result is:
Important:
I can't remove constraints from TestButton and set frame inside TestView. This UIKit button is being reused in regular UIKit screens
How it can be solved? Why UIViewRepresentable ignores constraints of it's children?
SwiftUI and UIKit's layout constraints are not the same...
One approach is to override intrinsicContentSize in your TestButton instead of trying to set constraints.
Give this a try:
struct TestView: View {
var body: some View {
TestButtonWrapper()
.background(Color.red)
}
}
final class TestButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
translatesAutoresizingMaskIntoConstraints = false
setTitle("Hello", for: .normal)
// these are ignored:
//heightAnchor.constraint(equalToConstant: 200).isActive = true
//widthAnchor.constraint(equalToConstant: 300).isActive = true
}
// add this override
override var intrinsicContentSize: CGSize {
return .init(width: 300.0, height: 200.0)
}
}
struct TestButtonWrapper: UIViewRepresentable {
func makeUIView(context: Context) -> TestButton {
let view = TestButton()
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
return view
}
func updateUIView(_ uiView: TestButton, context: Context) {
}
}
Edit
For clarification...
When using UIKit auto-layout:
the constraints define the size
intrinsicContentSize has no effect
When using SwiftUI:
intrinsicContentSize defines the size
the constraints have no effect
We can use the same TestButton class in both environments:
// if this class is used in a UIKit auto-layout environment
// its width and height constraints will define the size of the button
// intrinsicContentSize will have no effect
// if this class is used in a SwiftUI environment
// its width and height constraints will have no effect
// intrinsicContentSize will define the size of the button (absent any other sizing actions)
final class TestButton: UIButton {
override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setup()
}
func setup() {
translatesAutoresizingMaskIntoConstraints = false
setTitle("Hello", for: .normal)
heightAnchor.constraint(equalToConstant: 200).isActive = true
widthAnchor.constraint(equalToConstant: 300).isActive = true
}
override var intrinsicContentSize: CGSize {
return .init(width: 300.0, height: 200.0)
}
}
So, for a SwiftUI implementation:
struct TestView: View {
var body: some View {
VStack(alignment: .center, spacing: 20.0) {
Text("SwiftUI")
TestButtonWrapper()
.background(Color.red)
}
}
}
struct TestButtonWrapper: UIViewRepresentable {
func makeUIView(context: Context) -> TestButton {
let view = TestButton()
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
return view
}
func updateUIView(_ uiView: TestButton, context: Context) {
}
}
and here is a Storyboard / UIKit implementation:
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
let label = UILabel()
label.text = "Storyboard / UIKit"
let btn1 = TestButton()
btn1.backgroundColor = .systemRed
let stackView = UIStackView()
stackView.axis = .vertical
stackView.alignment = .center
stackView.spacing = 20.0
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
let g = view.safeAreaLayoutGuide
NSLayoutConstraint.activate([
stackView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
stackView.centerYAnchor.constraint(equalTo: g.centerYAnchor),
])
stackView.addArrangedSubview(label)
stackView.addArrangedSubview(btn1)
}
}
Both produce the same output:
I have an issue when trying to convert a custom View that inherits from UIView into a SwiftUI view, the view takes the whole screen.
But it works pretty well if I inherit from UILabel instead of UIView.
Note This view is shown inside a UIHostingController.
class ViewController: UIViewController {
let hostingVC = UIHostingController(rootView: SwiftUIView())
override func viewDidLoad() {
super.viewDidLoad()
addChild(hostingVC)
view.addSubview(hostingVC.view)
hostingVC.didMove(toParent: self)
hostingVC.view.backgroundColor = .red
hostingVC.view.wh_activateConstraints(configuration: {
[
$0.topAnchor.constraint(equalTo: view.topAnchor),
$0.leadingAnchor.constraint(equalTo: view.leadingAnchor),
$0.trailingAnchor.constraint(equalTo: view.trailingAnchor),
$0.bottomAnchor.constraint(equalTo: view.bottomAnchor),
]
})
}
}
Example 1:
this will work
struct SwiftUIView: View {
var body: some View {
VStack {
CustomLabel()
.fixedSize(horizontal: false, vertical: true)
}
}
struct CustomLabel: UIViewRepresentable {
func updateUIView(_ uiView: UIViewType, context: Context) {
}
func makeUIView(context: Context) -> some UIView {
let view = UILabel()
view.text = "hello world"
view.backgroundColor = .green
return view
}
}
Example 2: this will not work
struct SwiftUIView: View {
var body: some View {
VStack {
CustomLabel()
.fixedSize(horizontal: false, vertical: true)
}
}
}
struct CustomLabel: UIViewRepresentable {
func updateUIView(_ uiView: UIViewType, context: Context) {
}
func makeUIView(context: Context) -> some UIView {
let view = CustomUIlabel()
view.backgroundColor = .green
return view
}
}
class CustomUIlabel: UIView {
init(){
super.init(frame: .zero)
let label = UILabel(frame: .zero)
label.text = "hello world"
label.translatesAutoresizingMaskIntoConstraints = false
addSubview(label)
NSLayoutConstraint.activate(
[
label.topAnchor.constraint(equalTo: topAnchor),
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.trailingAnchor.constraint(equalTo: trailingAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor)
]
)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And when adding .fixedSize(horizontal: false, vertical: true) for second example the custom view will disappear.
and If I comment it, the view will take the whole screen.
Is there any solution to determine the size dynamically of a custom view that inherits from UIView?
Help me in one of the two ways maybe:
How to solve the problem? or
How to understand the error message?
Project summary
So I'm learning about inputAccessoryView by making a tiny project, which has only one UIButton. Tapping the button summons the keyboard with inputAccessoryView which contains 1 UITextField and 1 UIButton. The UITextField in the inputAccessoryView will be the final firstResponder that is responsible for the keyboard with that inputAccessoryView
The error message
API error: <_UIKBCompatInputView: 0x7fcefb418290; frame = (0 0; 0 0); layer = <CALayer: 0x60000295a5e0>> returned 0 width, assuming UIViewNoIntrinsicMetric
The code
is very straightforward as below
The custom UIView is used as inputAccessoryView. It installs 2 UI outlets, and tell responder chain that it canBecomeFirstResponder.
class CustomTextFieldView: UIView {
let doneButton:UIButton = {
let button = UIButton(type: .close)
return button
}()
let textField:UITextField = {
let textField = UITextField()
textField.placeholder = "placeholder"
return textField
}()
required init?(coder: NSCoder) {
super.init(coder: coder)
initSetup()
}
override init(frame:CGRect) {
super.init(frame: frame)
initSetup()
}
convenience init() {
self.init(frame: .zero)
}
func initSetup() {
addSubview(doneButton)
addSubview(textField)
}
func autosizing(to vc: UIViewController) {
frame = CGRect(x: 0, y: 0, width: vc.view.frame.size.width, height: 40)
let totalWidth = frame.size.width - 40
doneButton.frame = CGRect(x: totalWidth * 4 / 5 + 20,
y: 0,
width: totalWidth / 5,
height: frame.size.height)
textField.frame = CGRect(x: 20,
y: 0,
width: totalWidth * 4 / 5,
height: frame.size.height)
}
override var canBecomeFirstResponder: Bool { true }
override var intrinsicContentSize: CGSize {
CGSize(width: 400, height: 40)
} // overriding this variable seems to have no effect.
}
Main VC uses the custom UIView as inputAccessoryView. The UITextField in the inputAccessoryView becomes the real firstResponder in the end, I believe.
class ViewController: UIViewController {
let customView = CustomTextFieldView()
var keyboardShown = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
customView.autosizing(to: self)
}
#IBAction func summonKeyboard() {
print("hello")
keyboardShown = true
self.becomeFirstResponder()
customView.textField.becomeFirstResponder()
}
override var canBecomeFirstResponder: Bool { keyboardShown }
override var inputAccessoryView: UIView? {
return customView
}
}
I've seen people on the internet says this error message will go away if I run on a physical phone. I didn't go away when I tried.
I override intrinsicContentSize of the custom view, but it has no effect.
The error message shows twice together when I tap summon.
What "frame" or "layer" does the error message refer to? Does it refer to the custom view's frame and layer?
If we use Debug View Hierarchy we can see that _UIKBCompatInputView is part of the (internal) view hierarchy of the keyboard.
It's not unusual to see constraint errors / warnings with internal views.
Since frame and/or intrinsic content size seem to have no effect, I don't think it can be avoided (nor does it seem to need to be).
As a side note, you can keep the "Done" button round by using auto-layout constraints. Here's an example:
class CustomTextFieldView: UIView {
let textField: UITextField = {
let tf = UITextField()
tf.font = .systemFont(ofSize: 16)
tf.autocorrectionType = .no
tf.returnKeyType = .done
tf.placeholder = "placeholder"
// textField backgroundColor so we can see its frame
tf.backgroundColor = .yellow
return tf
}()
let doneButton:UIButton = {
let button = UIButton(type: .close)
return button
}()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
autoresizingMask = [.flexibleHeight, .flexibleWidth]
[doneButton, textField].forEach { v in
v.translatesAutoresizingMaskIntoConstraints = false
addSubview(v)
}
NSLayoutConstraint.activate([
// constrain doneButton
// Trailing: 20-pts from trailing
doneButton.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
// Top and Bottom 8-pts from top and bottom
doneButton.topAnchor.constraint(equalTo: topAnchor, constant: 8.0),
doneButton.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -8.0),
// Width equal to default height
// this will keep the button round instead of oval
doneButton.widthAnchor.constraint(equalTo: doneButton.heightAnchor),
// constrain textField
// Leading: 20-pts from leading
textField.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
// Trailing: 8-pts from doneButton leading
textField.trailingAnchor.constraint(equalTo: doneButton.leadingAnchor, constant: -8.0),
// vertically centered
textField.centerYAnchor.constraint(equalTo: centerYAnchor),
])
}
}
class CustomTextFieldViewController: UIViewController {
let customView = CustomTextFieldView()
var keyboardShown = false
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func summonKeyboard() {
print("hello")
keyboardShown = true
self.becomeFirstResponder()
customView.textField.becomeFirstResponder()
}
override var canBecomeFirstResponder: Bool { keyboardShown }
override var inputAccessoryView: UIView? {
return customView
}
}
I'm trying to display a dynamically sized UITextView inside a stack view, but the text view is not adjusting to the size of the content.
First I have the arranged subview:
class InfoView: UIView {
private var title: String!
private var detail: String!
private var titleLabel: UILabel!
private var detailTextView: UITextView!
init(infoModel: InfoModel) {
self.title = infoModel.title
self.detail = infoModel.detail
super.init(frame: .zero)
configure()
setConstraint()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func configure() {
titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .rounded(ofSize: titleLabel.font.pointSize, weight: .bold)
titleLabel.textColor = .lightGray
titleLabel.sizeToFit()
titleLabel.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(titleLabel)
detailTextView = UITextView()
detailTextView.sizeToFit()
detailTextView.text = detail
detailTextView.font = UIFont.systemFont(ofSize: 19)
detailTextView.isEditable = false
detailTextView.textColor = .lightGray
detailTextView.isUserInteractionEnabled = false
detailTextView.isScrollEnabled = false
detailTextView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(detailTextView)
}
private func setConstraint() {
NSLayoutConstraint.activate([
titleLabel.topAnchor.constraint(equalTo: self.topAnchor),
titleLabel.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 5),
titleLabel.heightAnchor.constraint(equalToConstant: 40),
detailTextView.topAnchor.constraint(equalTo: titleLabel.bottomAnchor),
detailTextView.leadingAnchor.constraint(equalTo: self.leadingAnchor),
detailTextView.trailingAnchor.constraint(equalTo: self.trailingAnchor),
detailTextView.bottomAnchor.constraint(equalTo: self.bottomAnchor)
])
}
}
Then I implement the stack view in a view controller:
class MyViewController: UIViewController {
var infoModelArr: [InfoModel]!
var stackView: UIStackView!
var scrollView: UIScrollView!
init(infoModelArr: [InfoModel]) {
self.infoModelArr = infoModelArr
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
var infoViewArr = [InfoView]()
for infoModel in infoModelArr {
let infoView = InfoView(infoModel: infoModel)
infoViewArr.append(infoView)
}
stackView = UIStackView(arrangedSubviews: infoViewArr)
stackView.axis = .vertical
stackView.spacing = 10
stackView.distribution = .fillProportionally
stackView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: scrollView.topAnchor),
stackView.leadingAnchor.constraint(equalTo: view.layoutMarginsGuide.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: view.layoutMarginsGuide.trailingAnchor),
])
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
scrollView.contentSize = stackView.bounds.size
}
}
Finally, I call the view controller as following:
let myVC = MyViewController(infoModelArr: [InfoModel(title: "title", detail: "detail"), InfoModel(title: "title", detail: "detail")])
self.present(myVC, animated: true, completion: nil)
Notably, if I were to instantiate the stack view with a single arranged subview, the height of the stack view seems to be dynamically adjusted, but as soon as 2 or more subviews are introduced, the height doesn't reflect the content.
When I attempted to set the intrinsic size of the InfoView,
override func layoutSubviews() {
super.layoutSubviews()
height = titleLabel.bounds.height + detailTextView.bounds.height
}
var height: CGFloat! = 200 {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
let originalSize = super.intrinsicContentSize
return CGSize(width: originalSize.width, height: height)
}
detailTextView.bounds.height returns 0.
The fillProportionally distribution tries to scale the heights of the arranged subviews according to their intrinsic content size, as a proportion of of the stack view's height. e.g. if the stack view has a height of 120, and arranged subview A has an intrinsic height of 10, and arranged subview B has an intrinsic height of 20, then A and B will have a height of 40 and 80 respectively in the stack view.
Your stack view doesn't have a defined height, so fillProportionally doesn't make much sense here.
Instead, a distribution of fill should do the job:
stackView.distribution = .fill
(as an experiment, you can try adding a height constraint to the stack view, and you'll see how fillProportionally works)
I have this SwiftUI component
var body: some View {
let image = TypographyImage.image(for: typography, kind: kind)
let width = image.size.width + 4
let height = TypographyImage.height(for: typography) * (kind == .oneLine ? 1.0 : 2.0)
ScrollView(.horizontal) {
HStack {
Image(uiImage: image)
.frame(width: width, height: height, alignment: Alignment(horizontal: .center, vertical: .top))
.offset(y: TypographyImage.offset(for: typography))
.background(Color.red)
if isSwiftUI {
Text(text)
.typography(typography, theme: AmityTheme.shared)
.background(Color.red)
} else {
MyUILabel(text: text, typography: typography, theme: AmityTheme.shared).background(Color.red)
}
}
}
}
Which display this UI
Notice that in MyUILabel, I apply background red color.
The background color and alignment of MyUILabel inside HStack is display correctly when in MyUILabel I return a UILabel as first child view
As show below
struct MyUILabel: UIViewRepresentable {
var text: String
var typography: AmityTheme.Typography
var theme: AmityTheme
func makeUIView(context: Context) -> UILabel {
let view = UILabel(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
updateUIView(view, context: context)
return view
}
func updateUIView(_ view: UILabel, context: Context) {
view.attributedText = NSAttributedString(string: text, attributes: theme.textAttributes(for: typography, palette: .black))
}
}
However, if I change it to return my custom view like
struct MyUILabel: UIViewRepresentable {
var text: String
var typography: AmityTheme.Typography
var theme: AmityTheme
func makeUIView(context: Context) -> PaddedUILabel {
let view = PaddedUILabel(frame: .zero)
view.translatesAutoresizingMaskIntoConstraints = false
view.setContentHuggingPriority(.defaultHigh, for: .vertical)
view.setContentHuggingPriority(.defaultHigh, for: .horizontal)
updateUIView(view, context: context)
return view
}
func updateUIView(_ view: PaddedUILabel, context: Context) {
view.update(text: text, typography: typography, theme: theme)
}
}
class PaddedUILabel: UIView {
private var label: UILabel!
private var topConstraint: NSLayoutConstraint!
private var bottomConstraint: NSLayoutConstraint!
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
private func commonInit() {
let label = UILabel(frame: .zero)
label.translatesAutoresizingMaskIntoConstraints = false
label.numberOfLines = 0
label.setContentHuggingPriority(.defaultHigh, for: .vertical)
label.setContentHuggingPriority(.defaultHigh, for: .horizontal)
self.label = label
addSubview(label)
topConstraint = label.topAnchor.constraint(equalTo: topAnchor)
bottomConstraint = label.bottomAnchor.constraint(equalTo: bottomAnchor)
let constraints: [NSLayoutConstraint] = [
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.trailingAnchor.constraint(equalTo: trailingAnchor),
topConstraint,
bottomConstraint
]
NSLayoutConstraint.activate(constraints)
}
func update(text: String, typography: AmityTheme.Typography, theme: AmityTheme) {
let attributes = theme.textAttributes(for: typography, palette: .black)
label.attributedText = NSAttributedString(string: text, attributes: attributes)
}
}
The result is
The background color is gone and HStack center vertical alignment is not respect. I can't seem to know why this happening.
Can anyone suggest me how to solve this?
Thanks
Seem like layout any constraint within CustomView and make it UIViewRepresentable will not work according to Here
In SwiftUI, parent asks child: what size do you want to be? Child tells parent, and parent sets size. Done.
AutoLayout however, needs more than one pass to determine the complete layout. It simply doesn't get that chance.
My rule-of-thumb for UIViewRepresentable is: keep it SUPER DUPER simple and only wrap a single view. More than one view is asking for layout problems.
Also this states that there is a limitation on how SwiftUI layout work which incompatible with vanilla layout constraint and intrinsincContentSize