Создание кастомного навигационного представления в SwiftUI.
Зададим собственное навигационное представление с помощью двух generic-типов. Первый тип — Content, который применяется для передачи представлений внутри кастомного навигационного представления. Второй тип — Destination, который используется для передачи любого целевого представления. struct CustomNavigationView<Content: View, Destination : View>: View {
} Content — универсальный тип, соответствующий представлению View. Он позволяет добавить закрытие, что дает не?
Создание кастомного навигационного представления в SwiftUI...
Зададим собственное навигационное представление с помощью двух generic-типов. Первый тип — Content
, который применяется для передачи представлений внутри кастомного навигационного представления. Второй тип — Destination
, который используется для передачи любого целевого представления.
struct CustomNavigationView<Content: View, Destination : View>: View {
}
Content
— универсальный тип, соответствующий представлениюView
. Он позволяет добавить закрытие, что дает несколько дочерних представлений.Destination
— универсальный тип, соответствующий представлениюView
. Он применяется для передачи целевой ссылки.
Константы и переменные
Добавьте в навигационное представление переменные и константы, приведенные ниже. Воспользуйтесь init()
, чтобы дополнить содержимое атрибутом @ViewBuilder
.
struct CustomNavigationView<Content: View, Destination : View>: View { let destination : Destination let isRoot : Bool let isLast : Bool let color : Color let content: Content @State var active = false @Environment(\.presentationMode) var mode: Binding<PresentationMode> init(destination: Destination, isRoot : Bool, isLast : Bool,color : Color, @ViewBuilder content: () -> Content) { self.destination = destination self.isRoot = isRoot self.isLast = isLast self.color = color self.content = content() } var body: some View { // некоторые представления } }
destination
необходимо для передачи целевого представления в кастомное навигационное представление. Если целевое представление отсутствует, задайте емуEmptyView()
.isRoot
— логическое значение для создания корневого представления. Оно понадобится для управления прозрачностью кнопки со стрелкой назад.isLast
— логическое значение для определения последнего представления. В данном руководстве это значение понадобится для управления прозрачностью кнопки с навигационной ссылкой.color
необходим для настройки цвета панели навигации.content
обеспечивает закрытие для передачи дочерних представлений в кастомное навигационное представление.active
— переменная состояния для управления навигационной ссылкой.mode
— обработчик переменных окружения для отклонения представлений.
Облик навигационной панели
Для панели навигации вы можете воспользоваться HStack
с указанием цвета фона или создать пользовательскую форму и разместить поверх элементы панели навигации. В этом руководстве мы создадим форму с очертаниями волны и поместим ее в ZStack
, а затем добавим HStack
с навигационными элементами.
struct WaveShape : Shape { func path(in rect: CGRect) -> Path { var path = Path() path.move(to: .zero) path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY)) path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) path.addCurve(to: CGPoint(x: rect.minX, y: rect.maxY), control1: CGPoint(x: rect.maxX * 0.75, y: rect.maxY * 0.5), control2: CGPoint(x: rect.maxX * 0.35, y: rect.maxY * 2)) return path } }
Тело кастомного представления навигации
В переменную body
добавьте GeometryReader
внутри NavigationView
. GeometryReader
понадобится, чтобы поправить размеры элементов пользовательского интерфейса. В GeometryReader
добавьте параметр color
, который расширит размер представления до размера экрана. Под color
добавьте VStack
, который будет содержать WaveShape()
, созданный нами выше. Вставьте его в ZStack
и отрегулируйте рамку. Чтобы удалить безопасную область в верхней части экрана, воспользуйтесь модификатором .edgesIgnoringSafeArea(.top)
.
В том же VStack
разместите константу content
с теми модификаторами по вашему выбору. Между всеми элементами в VStack
вставьте Spacer()
. Это должно переместить кастомную навигационную панель в верхнюю часть экрана, а содержимое — в центр экрана.
NavigationView
нужно только для того, чтобы заработала навигационная ссылка. Нам не понадобится панель навигации по умолчанию, которая поставляется вместе с NavigationView
. Чтобы удалить ее, добавьте модификатор .navigationBarHidden(true)
.
var body: some View { NavigationView { GeometryReader { geometry in Color.white VStack { ZStack { WaveShape() .fill(color.opacity(0.3)) // элементы панели навигации должны располагаться здесь, внутри HStack } .frame(width: geometry.size.width, height: 90) .edgesIgnoringSafeArea(.top) Spacer() self.content .padding() .background(color.opacity(0.3)) .cornerRadius(20) Spacer() } }.navigationBarHidden(true) } } }
Панель навигации
Последнее, что нам нужно, — добавить элементы в навигационную панель. Для этого руководства мы воспользуемся центрированным изображением, которое можно применить в качестве логотипа, и двумя изображениями со стрелками “влево” и “вправо” с жестами касания для навигации между представлениями. Убедитесь, что изображения одной ширины, и добавьте между ними Spacer()
.
Внутри жеста касания первой стрелки разместите переменную mode
, чтобы закрыть текущее представление. Внутри жеста касания второй стрелки подключите переменную active
, которую мы определили в начале этого руководства.
Добавьте NavigationLink
и передайте его переменным active
и destination
. Задайте представлению destination
модификатор .navigationBarHidden(true)
.
HStack { Image(systemName: "arrow.left") .frame(width: 30) .onTapGesture(count: 1, perform: { self.mode.wrappedValue.dismiss() }).opacity(isRoot ? 0 : 1) Spacer() Image(systemName: "command") .frame(width: 30) Spacer() Image(systemName: "arrow.right") .frame(width: 30) .onTapGesture(count: 1, perform: { self.active.toggle() }) .opacity(isLast ? 0 : 1) NavigationLink( destination: destination.navigationBarHidden(true) .navigationBarHidden(true), isActive: self.$active, label: { //никакого ярлыка }) } .padding([.leading,.trailing], 8) .frame(width: geometry.size.width) .font(.system(size: 22))
Содержимое
Создайте несколько представлений для навигации между ними. Внутри каждого создаваемого представления задействуйте CustomNavigationView
. В закрытии добавьте содержимое, которое должно отображаться в пользовательском интерфейсе.
Взгляните на пример ниже, чтобы понять, как заполнить параметры.
struct ContentView: View { var body: some View { CustomNavigationView(destination: FirstView(), isRoot: true, isLast: false, color: .blue){ Text("This is the Root View") } } } struct FirstView : View { var body: some View { CustomNavigationView(destination: SecondView(), isRoot: false, isLast: false, color: .red){ Text("This is the First View") } } } struct SecondView : View { var body: some View { CustomNavigationView(destination: LastView(), isRoot: false, isLast: false, color: .green){ Text("This is the Second View") } } } struct LastView : View { var body: some View { CustomNavigationView(destination: EmptyView(), isRoot: false, isLast: true, color: .yellow){ Text("This is the Last View") } } }
Чтобы ознакомиться с кодом целиком, нажмите сюда.