Я работаю над листом SwiftUI, размер которого может изменяться в зависимости от высоты содержимого. Лист работает хорошо, пока я не введу многострочный текст.
Чтобы справиться с динамической высотой, я добавил .fixedSize(horizontal: false, vertical: true)
к SheetHeightModifier
, что устраняет проблему с высотой. Однако когда я это делаю, я замечаю странное поведение анимации кнопки. Он подпрыгивает, когда вы нажимаете на него. Вот мой код.
Почему кнопка демонстрирует такое поведение с модификатором фиксированного размера?
struct SheetHeightModifier: ViewModifier {
@Binding var height: CGFloat
func body(content: Content) -> some View {
content
.fixedSize(horizontal: false, vertical: true)
.background(
GeometryReader { reader -> Color in
height = reader.size.height
print(height)
return Color.clear
}
)
}
}
struct PresentationDetentModifier: ViewModifier {
@Binding var height: CGFloat
func body(content: Content) -> some View {
content
.modifier(SheetHeightModifier(height: $height))
.presentationDetents([.height(height)])
}
}
extension View {
func flexiblePresentationDetents(height: Binding<CGFloat>) -> some View {
self.modifier(PresentationDetentModifier(height: height))
}
}
struct ContentView: View {
@State var showSheet = false
@State var sheetHeight: CGFloat = 0
var body: some View {
VStack {
Button("Present Sheet") {
showSheet = true
}
}
.sheet(isPresented: $showSheet) {
VStack {
Text("Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.")
}
.padding()
.flexiblePresentationDetents(height: $sheetHeight)
}
.padding()
}
}
Судя по журналам, высота, считываемая устройством чтения геометрии, изначально составляет более 8000 пикселей на долю секунды, затем изменяется примерно до 200, что является правильным.
Из-за такой большой высоты представление, представляющее лист , немного сжимается (аналогично тому, как вы используете фиксатор .large
), затем очень быстро после этого высота снова снижается, и представление возвращается в исходное положение. Это приводит к тому, что представления немного покачиваются, потому что они снова размещаются.
Простой способ обойти эту проблему — просто отфильтровать эти бессмысленные высоты из программы чтения геометрии. Если вы уверены, что размер контента никогда не превысит некоторого значения, вы можете игнорировать высоты выше этого значения. Например, здесь я использовал 2000
// I also changed this to use a preference value instead of
// directly assigning to 'height' in the GeometryReader
struct HeightKey: PreferenceKey {
static var defaultValue: CGFloat? { nil }
static func reduce(value: inout CGFloat?, nextValue: () -> CGFloat?) {
if let next = nextValue() {
value = next
}
}
}
struct SheetHeightModifier: ViewModifier {
@Binding var height: CGFloat
func body(content: Content) -> some View {
content
.fixedSize(horizontal: false, vertical: true)
.background {
GeometryReader { reader in
Color.clear
.preference(key: HeightKey.self, value: reader.size.height)
}
}
.onPreferenceChange(HeightKey.self) {
// the max height is 2000 - assume everything above that is nonsensical
guard let newHeight = $0, newHeight < 2000 else {
return
}
height = newHeight
}
}
}
Или вы можете использовать min
, чтобы ограничить высоту значением, которое не приведет к перемещению представления представления:
.onPreferenceChange(HeightKey.self) {
guard let newHeight = $0 else {
return
}
height = min(newHeight, UIScreen.main.bounds.height * 0.88)
}
К сожалению, в отличие от этого поста, вы не можете использовать фиксатор .custom
, поскольку он не может принимать никаких параметров. Хотя у них есть доступ к EnvironmentValues
, они относятся к среде представления, а не к листу.
Оператор print
показывает, что Text
начинается с очень большой высоты. Если вы также напечатаете ширину, вы увидите, что причина большой высоты в том, что она начинается с очень маленькой ширины. Это, в свою очередь, может быть связано с тем, что при появлении листа выполняется какая-то анимация масштабирования.
Будет полезно, если вы примените минимальную ширину к content
внутри SheetHeightModifier
. Фактическая минимальная ширина будет зависеть от размера экрана. Однако минимальная ширина может быть меньше ширины экрана, поскольку лист на большом iPad не занимает всю ширину экрана.
В качестве минимальной ширины можно использовать ширину iPod (= 320). Эта ширина также меньше, чем у самого маленького iPad в режиме разделения экрана на одну треть, поэтому это должно быть довольно безопасно:
// SheetHeightModifier
content
.frame(minWidth: 320) // 👈 HERE
// other modifiers as before
Самая высокая версия iOS для iPod — iOS 15, поэтому, если вы поддерживаете только iOS 16 и выше, вы можете даже увеличить минимальную ширину, скажем, 340.