Я новичок в Swift UI, и мне не удается заставить определенный пользовательский интерфейс вести себя так, как я хочу.
У меня есть ScrollView
, в котором есть некоторый контент. Этот контент в VStack может содержать от 1–3 текстовых компонентов до более чем 60 компонентов.
Я хочу, чтобы представление прокрутки увеличивалось настолько, насколько это необходимо. Как только места не останется, просто продолжайте использовать максимально доступную высоту и используйте прокрутку.
Проблема в том, что, хотя у меня 6 элементов, ScrollView
занимает всю высоту и помещает текст ниже. Вы можете проверить красный фон и нажатие кнопки «Прямо под ScrollView».
struct ContentTestView: View {
var body: some View {
VStack {
// ScrollView should wrap content height...
ScrollView {
VStack(alignment: .leading) {
ForEach(0..<6) { index in
Text("Item \(index)")
.padding()
.background(Color.gray.opacity(0.3))
.cornerRadius(8)
.padding(.bottom, 4)
}
}
.frame(maxWidth: .infinity) // Fill max width
}
.background(.red)
Text("Just right below the ScrollView")
Spacer() // There should be a space if the scroll view content is not too big
Text("Bottom")
.padding()
.frame(maxWidth: .infinity) // Fill max width
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentTestView()
}
}
Я пытался использовать fixedSize()
, но это работает не так, как я ожидал, поскольку подталкивает других братьев и сестер VStack и не прокручивает так, как я хочу.
Какое здесь решение? Я хочу, чтобы ScrollView
рос настолько, насколько ему нужно, и как только контент заполнит всю высоту, он должен выглядеть как предыдущее изображение, которое я вам показывал.
На Android я мог бы добиться этого, используя .weight(1f, fill = false)
в ScrollView
, но здесь подобного подхода нет.
Вы также можете попробовать обернуть ScrollView
с помощью OptimalHeight
, что представляет собой специальную реализацию Layout
, которая использует идеальную высоту, когда это возможно, но не превышая высоту контейнера. Это можно найти в ответе на Как заставить ScrollView сжиматься и занимать минимум места без хаков
Привет, @BenzyNeez, посмотрю. ViewThatFits
уже исправляет проблему для >= iOS 16. Всем спасибо!!
Я сейчас заплачу, я нашел решение в какой-то статье и адаптировал его. Мне было так больно использовать Swift UI. Мне все еще нужно привыкнуть к тому, как они решают проблемы, которые должны быть намного проще, лол.
Используйте GeometryReader в содержимом ScrollView и сохраните этот размер в состоянии. Затем используйте сохраненную высоту размера в ScrollView.
struct ContentTestView: View {
@State
private var scrollViewContentSize: CGSize = .zero
var body: some View {
VStack {
// ScrollView should wrap content height...
ScrollView {
VStack(alignment: .leading) {
ForEach(0..<5) { index in
Text("Item \(index)")
.padding()
.background(Color.gray.opacity(0.3))
.cornerRadius(8)
.padding(.bottom, 4)
}
}
.background(
GeometryReader { geo -> Color in
DispatchQueue.main.async {
scrollViewContentSize = geo.size
}
return Color.clear
}
)
.frame(maxWidth: .infinity) // Fill max width
}
.frame(
maxHeight: scrollViewContentSize.height
)
.background(.red)
Text("Just right below the ScrollView")
Spacer()
Text("Bottom")
.padding()
.frame(maxWidth: .infinity) // Fill max width
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentTestView()
}
}
Как сказал Бензи Низ в комментариях, вы можете использовать ViewThatFits
, если у вас iOS 16+.
Напишите контент, который вы хотите отобразить в качестве первого просмотра, в ViewThatFits
, затем напишите то же самое, но завернув в ScrollView
, в качестве второго просмотра.
Здесь это реализовано как ViewModifier
.
struct FixedSizeScrollView: ViewModifier {
let axis: Axis.Set
init(axis: Axis.Set) {
self.axis = axis
}
func body(content: Content) -> some View {
ViewThatFits(in: axis) {
content
ScrollView(axis) {
content
}
}
}
}
extension View {
func fixedSizeScrollView(_ axis: Axis.Set = .vertical) -> some View {
modifier(FixedSizeScrollView(axis: axis))
}
}
Использование:
VStack(alignment: .leading) {
ForEach(0..<6, id: \.self) { index in
Text("Item \(index)")
.padding()
.background(Color.gray.opacity(0.3))
.cornerRadius(8)
.padding(.bottom, 4)
}
}
.frame(maxWidth: .infinity)
.fixedSizeScrollView()
.background(.red)
Если вы хотите, чтобы контент можно было прокручивать, даже если он достаточно мал, чтобы поместиться, вы можете вместо этого написать представление прокрутки fixedSize
в качестве первого представления:
func body(content: Content) -> some View {
ViewThatFits(in: axis) {
ScrollView(axis) {
content
}
.fixedSize(
horizontal: axis.contains(.horizontal),
vertical: axis.contains(.vertical)
)
ScrollView(axis) {
content
}
}
}
Спасибо всем, это уже обеспечивает ожидаемое поведение. Довольно подробное объяснение!
Возможно, вы захотите использовать
ViewThatFits
для переключения междуVStack
, который не нужно прокручивать, и тем, который нужно прокручивать.