Site icon AppTractor

Как сделать так, чтобы размер всплывающего окна SwiftUI автоматически подстраивался под содержимое

Всплывающее окно (sheet) в iOS по умолчанию занимает примерно половину экрана, и хотя вы можете указать пользовательские значения, этого недостаточно. В этой статье показано, как легко настроить размер всплывающего окна под его содержимое.

Основы

При отображении всплывающего «листа» с кастомными значениями фиксаций в SwiftUI обычно получается что-то вроде:

.sheet(isPresented: $isPresented) {
    MySheet()
        .presentationDetents([.medium, .large])
}

Это инициализирует лист со средним размером и предоставит пользователю возможность изменять его размер, проводя пальцем по маркеру. Также можно указать фиксированные значения .height и .fraction.

Проблема

Хотя поддержка кастомных значений позиционного фиксатора была отличным дополнением к SwiftUI в iOS 16, существуют ситуации, когда предопределенных размеров недостаточно.

Например, если у вас есть лист с определенным представлением, которое должно иметь фиксированный размер, короткое представление с .medium будет занимать лишнее пространство, а высокое представление с .medium будет обрезано.

Это может быть удобно для листов на основе ScrollView, где прокручиваемое представление будет изменять размер в соответствии с размером и позволит пользователям прокручивать контент, но для непрокручиваемых представлений нам нужно что-то большее.

Так разве не было бы здорово, если бы лист можно было настроить под размер его содержимого? В настоящее время такой встроенной опции нет, но мы можем легко ее реализовать.

Реализация

Поскольку параметр .sizeToFit нельзя выразить с помощью стандартного типа PresentationDetent, нам необходимо создать собственный тип параметра и обеспечить его взаимодействие с нативным.

enum SizeToFitPresentationDetent {
    
    case sizeToFit
}

На данный момент этот перечислимый тип имеет только один вариант, но мы всё же используем его, чтобы сделать место вызова более чистым и позволить нам расширять его в будущем.

Нам нужен пользовательский ViewModifier, который позволит нам обрабатывать это ограничение, а также набор дополнительных нативных ограничений, чтобы пользователь мог изменять размер окна, когда это необходимо.

struct SizeToFitModifier: ViewModifier {

    let additional: Set<PresentationDetent>

    @State private var contentHeight = 0.0

    func body(content: Content) -> some View {
        content
            .onGeometryChange(for: CGFloat.self) {
                $0.size.height
            } action: { height in
                contentHeight = height
            }
            .presentationDetents(Set([.height(contentHeight)]).union(additional))
    }
}

Модификатор отслеживает размер содержимого и записывает высоту в свойство состояния, которое применяется в качестве собственного параметра .height с помощью собственного модификатора .presentationDetents.

Благодаря возможности определения дополнительных параметров additional, размер листа по умолчанию будет соответствовать содержимому, но при этом пользователи смогут изменять размер листа, перетаскивая маркер.

Теперь мы можем создать функцию модификатора представления SwiftUI, которая будет применять этот модификатор представления «под капотом».

extension View {

    /// Sets the sheet detent to fit its content height.
    func presentationDetents(
        _ detent: SizeToFitPresentationDetent,
        additional: Set<PresentationDetent> = []
    ) -> some View {
        modifier(SizeToFitModifier(additional: additional))
    }
}

Используя эти базовые элементы, мы теперь можем легко применить пользовательский параметр фиксатора .sizeToFit:

.sheet(isPresented: $isPresented) {
    MySheet()
        .presentationDetents(.sizeToFit)
}

Это позволит подогнать размер листа под его содержимое без дополнительных поддерживаемых фиксаторов, но мы можем легко определить дополнительные фиксаторы следующим образом:

.sheet(isPresented: $isPresented) {
    MySheet()
        .presentationDetents(.sizeToFit, additional: [.medium, .large])
}

Лист (sheet) будет автоматически менять размер при изменении контента, при этом пользователь всё ещё сможет вручную изменять его высоту, перетаскивая sheet между доступными фиксаторами, которые мы задали.

Заключение

Как вы видели в этом посте, добавление поддержки функции «размер по содержимому» по сути сводится к отслеживанию размера содержимого и применению этого размера в качестве фиксатора .height.

Пакет PresentationKit содержит эту реализацию и позволяет применять элемент .sizeToFit с кастомным модификатором представления presentationDetents(_:additional:) без какого-либо дополнительного кода.

Источник

Exit mobile version