Два года назад я написал пост, посвященный созданию кастомного bottom sheet в SwiftUI. Сегодня же необходимости писать его вручную с нуля больше нет, по крайней мере, если вам не нужно какое-нибудь супер-уникальное поведение. SwiftUI теперь предоставляет новый API для отображения bottom sheet всего в несколько строк кода. В этом посте мы рассмотрим новый API, позволяющий нам отображать различные вариации bottom sheet.

Новый API SwiftUI для отображения bottom sheet достаточно прост в использовании. Все, что нам нужно сделать, это прикрепить модификатор представления presentationDetents к содержимому модификатора представления sheet.

struct ContentView: View {
    @State private var sheetShown = false
    @State private var query = ""

    var body: some View {
        Button("Display bottom sheet") {
            sheetShown = true
        }
        .sheet(isPresented: $sheetShown) {
            NavigationStack {
                Text("You query: \(query)")
                    .searchable(text: $query)
                    .navigationTitle("Search")
            }
            .presentationDetents([.medium])
        }
    }
}

Чтобы узнать, как создать bottom sheet в SwiftUI с нуля, читайте мой пост “Создание Bottom Sheet в SwiftUI”.

Как вы можете видеть в приведенном выше примере, мы используем модификатор представления presentationDetents для отображения sheet в качестве bottom sheet. Мы также устанавливаем размер bottom sheet на medium. В этом случае она займет половину экрана. Вы можете указать в модификаторе представления presentationDetent массив допустимых размеров, тем самым позволяя пользователю изменять размер sheet, перетаскивая его.

struct ContentView: View {
    @State private var sheetShown = false
    @State private var query = ""

    var body: some View {
        Button("Display bottom sheet") {
            sheetShown = true
        }
        .sheet(isPresented: $sheetShown) {
            NavigationStack {
                Text("You query: \(query)")
                    .searchable(text: $query)
                    .navigationTitle("Search")
            }
            .presentationDetents([.medium, .large])
        }
    }
}

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

struct ContentView: View {
    @State private var sheetShown = false
    @State private var query = ""

    var body: some View {
        Button("Display bottom sheet") {
            sheetShown = true
        }
        .sheet(isPresented: $sheetShown) {
            NavigationStack {
                Text("You query: \(query)")
                    .searchable(text: $query)
                    .navigationTitle("Search")
            }
            .presentationDetents(
                [
                    .fraction(0.2),
                    .height(300),
                    .fraction(0.5),
                    .height(600)
                ]
            )
        }
    }
}

Как вы можете видеть в приведенном выше примере, мы используем функцию fraction, чтобы установить высоту sheet на определенную часть доступного пространства. Вы также можете использовать функцию height, чтобы установить высоту фиксированного размера. Вы также можете смешивать различные варианты для установки высоты bottom sheet, чтобы добиться нужного вам внешнего вида.

В зависимости от текущей среды нам может потребоваться создать супер-индивидуальную логику изменения размера. Например, я хотел бы отобразить bottom sheet, заняв 80% доступного пространства на iPad и в полную высоту на iPhone. Мы можем реализовать эту кастомную презентационную логику и повторно использовать ее на нескольких экранах.

struct MyCustomDetent: CustomPresentationDetent {
    static func height(in context: Context) -> CGFloat? {
        if context.verticalSizeClass == .regular {
            return context.maxDetentValue * 0.8
        } else {
            return context.maxDetentValue
        }
    }
}

Во-первых, мы должны создать тип, соответствующий протоколу CustomPresentationDetent. Во-вторых, мы должны реализовать единственную необходимую функцию с названием height, которая принимает context в качестве параметра. Параметр context содержит все значения среды SwiftUI, и вы можете использовать его, чтобы определить, какая высота вам нужна в конкретной среде. В приведенном выше примере, чтобы определить высоту sheet, я использую verticalSizeClass.

struct ContentView: View {
    @State private var sheetShown = false
    @State private var query = ""

    var body: some View {
        Button("Display bottom sheet") {
            sheetShown = true
        }
        .sheet(isPresented: $sheetShown) {
            NavigationStack {
                Text("You query: \(query)")
                    .searchable(text: $query)
                    .navigationTitle("Search")
            }
            .presentationDetents([.medium, .custom(MyCustomDetent.self)])
        }
    }
}

Изменить размер bottom sheet можно не только с помощью перетаскивания, но и программным способом. В этом случае вы должны передать @Binding выбранному размеру из допустимых.

struct ContentView: View {
    @State private var sheetShown = false
    @State private var sheetSize: PresentationDetent = .medium

    var body: some View {
        VStack {
            Picker("Size", selection: $sheetSize) {
                Text("Medium").tag(PresentationDetent.medium)
                Text("Large").tag(PresentationDetent.large)
            }
            
            Button("Display bottom sheet") {
                sheetShown = true
            }
            .sheet(isPresented: $sheetShown) {
                NavigationStack {
                    Text("Sheet content")
                }
                .presentationDetents([.medium, .large], selection: $sheetSize)
            }
        }
    }
}

Последнее, что мы можем настроить, - это индикатор перетаскивания bottom sheet. Мы можем сделать его всегда видимым, скрытым или разрешить решать, должен ли он отображаться, SwiftUI.

struct ContentView: View {
    @State private var sheetShown = false
    @State private var query = ""

    var body: some View {
        Button("Display bottom sheet") {
            sheetShown = true
        }
        .sheet(isPresented: $sheetShown) {
            NavigationStack {
                Text("You query: \(query)")
                    .searchable(text: $query)
                    .navigationTitle("Search")
            }
            .presentationDetents([.medium, .large])
            .presentationDragIndicator(.automatic)
        }
    }
}

Сегодня мы узнали, как использовать новый bottom sheet API в SwiftUI. Мне нравится уровень кастомизации, который он обеспечивает. Надеюсь, вам понравился этот пост, не стесняйтесь подписываться на меня в Твиттере и задавать вопросы, связанные с этой темой.


Всех желающих приглашаем на открытый урок «Делаем сетевой слой в приложении iOS». На уроке рассмотрим, как и с помощью каких решений написать сетевой слой приложения iOS и как делать сетевые запросы. Регистрация открыта по ссылке.

Комментарии (1)


  1. evaganov
    02.11.2022 19:17

    К сожалению всё это работает только начиная с iOS 16. Если нужна поддержка более старых версий, приходится всё так же писать вручную.