Один из лучших способов изучить SwiftUI (как и большинство технологий) — реализовать что-нибудь маленькое и полезное.
Всплывающее окно является отличным вариантом — это один из самых популярных инструментов интерфейса, который часто приходится настраивать, если родной вариант неудовлетворительный по внешнему виду или функциональности.
В этом уроке мы создадим минимальное всплывающее окно в SwiftUI с нуля.
В результате мы получим работающий компонент пользовательского интерфейса, который вы сможете реализовать в своем приложении SwiftUI или продолжить его настройку и улучшение.
Вот как это будет выглядеть:
Выбор API
Для начала давайте определимся с функционалом всплывающего окна, которое мы хотим реализовать.Это будет элемент, который будет отображаться при нажатии кнопки.
Он просто отобразит метку.
Он будет анимированно появляться и исчезать.
Нам нужна возможность определять внешний вид и стиль всплывающего окна, добавлять его в стек интерфейса и контролировать его состояние.
Мы начнем с написания примера использования всплывающего окна, как если бы оно уже существовало, а к самой реализации перейдем чуть позже.
Такой подход позволит вам сразу понять, как будет удобнее использовать компонент в вашем коде.
Вот что делает этот простой пример:struct ContentView : View { @State var showingPopup = false // 1 var body: some View { ZStack { Color.red.opacity(0.2) Button("Push me") { showingPopup = true // 2 } } .
popup(isPresented: $showingPopup) { // 3 ZStack { // 4 Color.blue.frame(width: 200, height: 100) Text("Popup!") } } } }
- Мы добавляем всплывающее окно в качестве модификатора к нашему элементу, передавая @Binding показ всплывающего окна для управления состоянием внутри реализации самого элемента.
Переменная @Состояние вар показ всплывающего окна будет управлять отображением всплывающего окна.
- Отдельная кнопка на экране будет изменять состояние переменной showPopup.
- Мы добавляем всплывающее окно в качестве модификатора к нашему элементу, передавая @Binding показ всплывающего окна для управления состоянием внутри реализации самого элемента.
- Дизайн и содержимое всплывающего окна также передаются в качестве параметра.
- Само всплывающее окно реализовано как Модификатор просмотра , как это принято в SwiftUI.
Реализация модификатора представления extension View {
public func popup<PopupContent: View>(
isPresented: Binding<Bool>,
view: @escaping () -> PopupContent) -> some View {
self.modifier(
Popup(
isPresented: isPresented,
view: view)
)
}
}
Этот фрагмент кода не требует пояснений — это определение модификатора всплывающего окна для представления.
Мы уже знаем, что нам нужны два параметра — Представлен , который представляет Обертка свойств привязки для управления состоянием всплывающего окна и вид , который отвечает за появление всплывающего окна.
Теперь мы можем перейти к самому интересному.
Реализация всплывающего элемента
Инициализатор и общедоступные переменные понятны — они уже были определены и объяснены, когда мы выбрали API: public struct Popup<PopupContent>: ViewModifier where PopupContent: View {
init(isPresented: Binding<Bool>,
view: @escaping () -> PopupContent) {
self._isPresented = isPresented
self.view = view
}
/// Controls if the sheet should be presented or not
@Binding var isPresented: Bool
/// The content to present
var view: () -> PopupContent
Список частных переменных расскажет вам больше о деталях реализации — всплывающее окно будет отображаться и скрываться путем изменения смещения содержащего его представления.
Это смещение легко вычислить и анимировать, и оно подходит для всех размеров экрана, поскольку напрямую от него зависит. Приватные переменные будут содержать данные кадра, необходимые для отображения и скрытия всплывающего окна — кадры родительского контроллера и содержимого всплывающего окна, смещения для скрытого и отображаемого состояний, а также вспомогательные методы для получения размера экрана.
// MARK: - Private Properties
/// The rect of the hosting controller
@State private var presenterContentRect: CGRect = .
zero /// The rect of popup content @State private var sheetContentRect: CGRect = .
zero
/// The offset when the popup is displayed
private var displayedOffset: CGFloat {
-presenterContentRect.midY + screenHeight/2
}
/// The offset when the popup is hidden
private var hiddenOffset: CGFloat {
if presenterContentRect.isEmpty {
return 1000
}
return screenHeight - presenterContentRect.midY + sheetContentRect.height/2 + 5
}
/// The current offset, based on the "presented" property
private var currentOffset: CGFloat {
return isPresented ? displayedOffset : hiddenOffset
}
private var screenWidth: CGFloat {
UIScreen.main.bounds.size.width
}
private var screenHeight: CGFloat {
UIScreen.main.bounds.size.height
}
Пользовательский контент самого всплывающего окна минимален: мы читаем фрейм основного контента, затем добавляем лист наложение, содержащее всплывающее окно.
Сам лист делает почти то же самое — он считывает свой кадр, чтобы позиционировать видимое всплывающее окно пользовательского интерфейса, а также добавляет обработчик для удаления отображаемого всплывающего окна при нажатии и простую анимацию.
Ранее рассчитанное текущееСмещение : // MARK: - Content Builders
public func body(content: Content) -> some View {
ZStack {
content
.
frameGetter($presenterContentRect) } .
overlay(sheet()) } func sheet() -> some View { ZStack { self.view() .
simultaneousGesture( TapGesture().
onEnded { dismiss() }) .
frameGetter($sheetContentRect) .
frame(width: screenWidth) .
offset(x: 0, y: currentOffset) .
animation(Animation.easeOut(duration: 0.3), value:
currentOffset)
}
}
private func dismiss() {
isPresented = false
}
Чтобы реализовать простую анимацию в SwiftUI, достаточно добавить однострочный модификатор, что мы и делаем в конце создания.
лист .
Естественно, его можно анимировать по вашему желанию — просто заменив окончательный размер и положение на экране, вы можете получить другой тип элемента пользовательского интерфейса (например, верхний или нижний тост).
Скорее всего, вы заметили модификатор FrameGetter. Это некрасивый, но необходимый метод получения фрейма в SwiftUI (по крайней мере, судя по документации, более удобного способа получения фрейма в SwiftUI у нас нет).
Надеемся, в будущем появится более удобный способ: extension View {
func frameGetter(_ frame: Binding<CGRect>) -> some View {
modifier(FrameGetter(frame: frame))
}
}
struct FrameGetter: ViewModifier {
@Binding var frame: CGRect
func body(content: Content) -> some View {
content
.
background( GeometryReader { proxy -> AnyView in let rect = proxy.frame(in: .
global)
// This avoids an infinite layout loop
if rect.integral != self.frame.integral {
DispatchQueue.main.async {
self.frame = rect
}
}
return AnyView(EmptyView())
})
}
}
GeometryReader — это представление в SwiftUI, которое принимает координатное пространство в качестве параметра и предоставляет информацию о его размере содержимому (другими словами, именно то, что необходимо для получения данных о кадре).
Заключение
Приведенный выше код — это все, что нам нужно для простой версии всплывающего окна, и большая его часть понятна и легко читается.Этот код может стать основой для более сложной реализации всплывающего окна, если вам нужно что-то более индивидуальное.
Вы также можете посмотреть наш библиотека для всплывающего окна в SwiftUI. По сути, это руководство представляет собой упрощенную версию кода из нашего репозитория.
Он заметно сложнее из-за большего количества стилей всплывающих окон, расширенных параметров и обратных вызовов, а также поддержки нескольких платформ.
Код для отображения и добавления всплывающего окна идентичен приведенному выше.
Библиотека предоставляет как простые всплывающие окна:
и более сложные пользовательские представления:
Теги: #с открытым исходным кодом #Swift #swiftUI #switch #popup #popup
-
Эль
19 Oct, 24 -
В Конце Концов, Фрилансеры — Трудоголики.
19 Oct, 24 -
Чего Ожидать От Первой Работы
19 Oct, 24 -
Sip Телефон На Stm32F7-Discovery
19 Oct, 24