Как сделать так, чтобы представление SwiftUI UIViewRepresentable охватило его содержимое?
Я пытаюсь заставить SwiftUI распознавать внутренний размер UIViewRepresentable, но, похоже, он обрабатывает его так, как будто для фрейма установлено значение maxWidth: .infinity, maxHeight: .infinity
. Я создал надуманный пример, вот мой код:
struct Example: View {
var body: some View {
VStack {
Text("Hello")
TestView()
}
.background(Color.red)
}
}
struct TestView: UIViewRepresentable {
func makeUIView(context: UIViewRepresentableContext<TestView>) -> TestUIView {
return TestUIView()
}
func updateUIView(_ uiView: TestUIView, context: UIViewRepresentableContext<TestView>) {
}
typealias UIViewType = TestUIView
}
class TestUIView: UIView {
required init?(coder: NSCoder) { fatalError("-") }
init() {
super.init(frame: .zero)
let label = UILabel()
label.text = "Sed mattis urna a ipsum fermentum, non rutrum lacus finibus. Mauris vel augue lorem. Donec malesuada non est nec fermentum. Integer at interdum nibh. Nunc nec arcu mauris. Suspendisse efficitur iaculis erat, ultrices auctor magna."
label.numberOfLines = 0
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .purple
addSubview(label)
translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
widthAnchor.constraint(equalToConstant: 200),
label.leadingAnchor.constraint(equalTo: leadingAnchor),
label.trailingAnchor.constraint(equalTo: trailingAnchor),
label.topAnchor.constraint(equalTo: topAnchor),
label.bottomAnchor.constraint(equalTo: bottomAnchor),
])
}
}
struct Example_Previews: PreviewProvider {
static var previews: some View {
Example()
.previewDevice(PreviewDevice(rawValue: "iPhone 8"))
.previewLayout(PreviewLayout.fixed(width: 300, height: 300))
}
}
Что я получаю:
Могу ли я сделать так, как я ожидал?
РЕДАКТИРОВАТЬ: после дальнейшего осмотра кажется, что я получаю Unable to simultaneously satisfy constraints.
и одним из ограничений является 'UIView-Encapsulated-Layout-Height'
ограничение на растянутый размер. Так что, я думаю, это может помочь предотвратить навязывание этих ограничений моему мнению? Не уверен...
2 ответа
Использовать .fixedSize()
решает проблему для меня.
То, что я сделал, использует UIViewController
, поэтому для UIView
. Я попытался создать сэндвич-структуру вроде:
SwiftUI => UIKit => SwiftUI
Рассмотрим простое представление SwiftUI:
struct Block: View {
var text: String
init(_ text: String = "1, 2, 3") {
self.text = text
}
var body: some View {
Text(text)
.padding()
.background(Color(UIColor.systemBackground))
}
}
Цвет фона устанавливается для проверки того, передаются ли значения внешней среды SwiftUI внутреннему SwiftUI. Общий ответ - да, но следует соблюдать осторожность, если вы используете такие вещи, как.popover
.
Одна из приятных особенностей UIHostingController заключается в том, что вы можете получить естественный размер SwiftUI, используя .intrinsicContentSize
. Это работает для узких представлений, таких какText
а также Image
, или контейнер, содержащий такие тесные виды. Однако я не уверен, что будет сGeometryReader
.
Рассмотрим следующий контроллер представления:
class VC: UIViewController {
let hostingController = UIHostingController(rootView: Block("Inside VC"))
var text: String = "" {
didSet {
hostingController.rootView = Block("Inside VC: \(text).")
}
}
override func viewDidLoad() {
addChild(hostingController)
view.addSubview(hostingController.view)
view.topAnchor.constraint(equalTo: hostingController.view.topAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor).isActive = true
view.leadingAnchor.constraint(equalTo: hostingController.view.leadingAnchor).isActive = true
view.trailingAnchor.constraint(equalTo: hostingController.view.trailingAnchor).isActive = true
view.translatesAutoresizingMaskIntoConstraints = false
hostingController.view.translatesAutoresizingMaskIntoConstraints = false
}
override func viewDidLayoutSubviews() {
preferredContentSize = view.bounds.size
}
}
Здесь нет ничего особенно интересного. Сворачиваем маски для автоматического изменения ОБА нашегоUIView
и хостинг SwiftUI UIView
. Мы заставляем наше представление иметь такой же естественный размер, как и у SwiftUI.
Я бы сказал об обновлении preferredContentSize
позже.
Скучный VCRep
:
struct VCRep: UIViewControllerRepresentable {
var text: String
func makeUIViewController(context: Context) -> VC {
VC()
}
func updateUIViewController(_ vc: VC, context: Context) {
vc.text = text
}
typealias UIViewControllerType = VC
}
Теперь перейдем к ContentView, то есть к внешнему SwiftUI:
struct ContentView: View {
@State var alignmentIndex = 0
@State var additionalCharacterCount = 0
var alignment: HorizontalAlignment {
[HorizontalAlignment.leading, HorizontalAlignment.center, HorizontalAlignment.trailing][alignmentIndex % 3]
}
var additionalCharacters: String {
Array(repeating: "x", count: additionalCharacterCount).joined(separator: "")
}
var body: some View {
VStack(alignment: alignment, spacing: 0) {
Button {
self.alignmentIndex += 1
} label: {
Text("Change Alignment")
}
Button {
self.additionalCharacterCount += 1
} label: {
Text("Add characters")
}
Block()
Block().environment(\.colorScheme, .dark)
VCRep(text: additionalCharacters)
.fixedSize()
.environment(\.colorScheme, .dark)
}
}
}
И волшебным образом все работает. Вы можете изменить горизонтальное выравнивание или добавить больше символов в контроллер представления и ожидать правильного макета.
На что обратить внимание:
- Вы должны указать
.fixedSize()
использовать натуральный размер. В противном случае контроллер представления занимал бы как можно больше места, как GeometryReader(). Это, наверное, неудивительно, учитывая.fixedSize()
документацию, но название ужасное. Я бы никогда не ожидал.fixedSize()
иметь в виду это. - Вы должны установить
preferredContentSize
и держите его в курсе. Сначала я обнаружил, что это не оказывает визуального воздействия на мой код, но горизонтальное выравнивание кажется неправильным, потому чтоVCRep
всегда имеет ведущий якорь, используемый в качестве руководства по макету дляVStack
. После проверки размеров вида через.alignmentGuide
, Оказывается, чтоVCRep
имеет нулевой размер. Это все еще видно! Поскольку по умолчанию обрезка контента не включена!
Кстати, если у вас есть ситуация, когда высота просмотра зависит от его ширины, и вам посчастливилось получить ширину (через GeometryReader
), вы также можете попробовать создать макет самостоятельно прямо в представлении SwiftUI body
, и прикрепив свой UIView как .background
служить художником по индивидуальному заказу. Это работает, например, если вы используете TextKit для расчета макета текста.
Пожалуйста, попробуйте код ниже, это поможет вам
// **** For getting label height ****
struct LabelInfo {
func heightForLabel(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.height
}
}
Изменить в примере
struct Example: View {
// Note : Width is fixed 200 at both place
// : use same font at both
let height = LabelInfo().heightForLabel(text: "Sed mattis urna a ipsum fermentum, non rutrum lacus finibus. Mauris vel augue lorem. Donec malesuada non est nec fermentum. Integer at interdum nibh. Nunc nec arcu mauris. Suspendisse efficitur iaculis erat, ultrices auctor magna.", font: UIFont.systemFont(ofSize: 17), width: 200)
var body: some View {
VStack {
Text("Hello")
TestView().frame(width: 200, height: height, alignment: .center) //You need to do change here
}
.background(Color.red)
}
}