Лучший опыт

Создание кастомного навигационного представления в 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")          }      }  }

Чтобы ознакомиться с кодом целиком, нажмите сюда.