В SwiftUI у меня есть такая последовательность: мне нужен особый способ: я провожу пальцем вверх от кадра 1 к кадру 2, а затем смахиваю влево к кадру 3. Это работает нормально, как и предполагалось.
Я сделал это, изменив представления, так что, по сути, есть два представления, и оба они используют общий кадр 2. Итак, у меня есть кадры 2a и 2b, по сути, одинаковые, но находящиеся в разных представлениях. Таким образом, я могу перейти от вертикальной прокрутки к горизонтальной прокрутке.
Это работает, как заявлено, но переход немного раздражает, потому что он проходит половину пути к 2a в firstView() и резко переключается на 2b во SecondView().
Я думаю, что мне нужен какой-то способ проверить ScrollTransitionPhase.isIdentity перед установкой View.id?
В целом я новичок в Swift, и это мои первые несколько попыток использовать SwiftUI iOS.
struct firstView: View {
@Binding var scrollId: Int?
var body: some View {
ScrollView(.vertical) {
VStack(spacing: 0) {
Rectangle()
.fill(.gray)
.overlay
{
Text("Frame 1 ")
.foregroundStyle(.white)
}
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
.id(1)
Rectangle()
.fill(.gray)
.overlay
{
Text("Frame 2a ")
.foregroundStyle(.white)
}
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
.id(2)
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
.scrollPosition(id: $scrollId)
Text("Hello World!")
}
}
struct secondView: View {
@Binding var scrollId: Int?
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 0) {
Rectangle()
.fill(.gray)
.overlay
{
Text("Frame 2b ")
.foregroundStyle(.white)
}
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
.id(3)
Rectangle()
.fill(.gray)
.overlay
{
Text("Frame 3 ")
.foregroundStyle(.white)
}
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
.id(4)
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
Text("Do you want to play a game?")
}
}
struct lastView: View {
var body: some View {
Text("Now is the time for all good men")
}
}
struct TestSwiftUIView4: View {
@State var scrollId: Int?
var body: some View {
switch scrollId {
case 4:
lastView()
case 2:
secondView(scrollId: $scrollId)
default:
firstView(scrollId: $scrollId)
}
}
}
В firstView
модификатор scrollPosition
использовался для привязки положения прокрутки к переменной scrollId
. Это означает, что когда ScrollView
в firstView
прокручивается вверх, переменная состояния scrollId
обновляется.
Переменная состояния, вероятно, обновляется в середине перехода. Однако когда это происходит, родительское представление TestSwiftUIView4
заменяет firstView
на secondView
. Вот почему происходят резкие изменения.
Вы можете немного смягчить переход, добавив модификатор .animation
к родительскому представлению. Для этого необходимо вложить оператор переключателя в Group
:
var body: some View {
Group {
switch scrollId {
case 4:
lastView()
case 2:
secondView(scrollId: $scrollId)
default:
firstView(scrollId: $scrollId)
}
}
.animation(.easeInOut, value: scrollId)
}
Однако я бы предположил, что всю эту конструкцию представления можно упростить:
ScrollView
в качестве родительского контейнера для дочерних представлений.scrollId
не нужно передавать в secondView
, поскольку она не обновляется. Действительно, кадрам 2 и 3 больше не нужны конкретные идентификаторы, поскольку на них никогда не ссылаются.lastView
, думаю, это был просто фиктивный вид, который никогда не предназначался для показа?Единственная сложность — отключить прокрутку по вертикали ScrollView
, когда Кадр 1 прокручивается вверх. Я начал с того, что попробовал следующее:
ScrollView(.vertical) {
// ...
}
.scrollDisabled(scrollId == 2)
Это работает, но иногда дает резкий переход, как это было раньше, в зависимости от того, насколько быстро прокручивается кадр 1.
Лучшее решение — использовать отдельную переменную состояния для управления отключенной прокруткой. Переменную состояния можно обновить, когда будет обнаружено, что secondView
был прокручен вверх. Для определения этого можно использовать GeometryReader
на фоне secondView
.
Фактически, если у вас есть флаг, который обновляется при прокрутке secondView
вверх, нет необходимости отслеживать scrollId
вообще. Это означает, что .scrollPosition
тоже можно отбросить вместе с другими модификаторами .id
.
Вот полностью переработанная версия:
struct firstView: View {
var body: some View {
Color.gray
.overlay { Text("Frame 1") }
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
}
}
struct secondView: View {
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 0) {
Color.gray
.overlay { Text("Frame 2") }
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
Color.gray
.overlay { Text("Frame 3") }
.padding(10)
.containerRelativeFrame([.horizontal, .vertical])
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
}
.scrollTargetLayout()
}
.scrollTargetBehavior(.paging)
}
}
struct TestSwiftUIView4: View {
@State private var isSecondViewShowing = false
private var scrollDetector: some View {
GeometryReader { proxy in
let minY = proxy.frame(in: .scrollView).minY
Color.clear
.onChange(of: minY) { oldVal, newVal in
if !isSecondViewShowing, newVal <= 0 {
isSecondViewShowing = true
}
}
}
}
var body: some View {
VStack {
ScrollView(.vertical) {
VStack(spacing: 0) {
firstView()
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
secondView()
.scrollTransition { content, phase in
content.opacity(phase.isIdentity ? 1: 0)
}
.background { scrollDetector }
}
.foregroundStyle(.white)
.scrollTargetLayout()
}
.scrollIndicators(.hidden)
.scrollTargetBehavior(.paging)
.scrollDisabled(isSecondViewShowing)
if isSecondViewShowing {
Text("Do you want to play a game?")
} else {
Text("Hello World!")
}
}
}
}
большое спасибо! Да, последний вид и вообще нижний текст были просто заполнителями. Это работает отлично и именно так, как я себе представлял. У меня есть более ранняя версия (вероятно, TestSwiftUIView3), где я попытался сделать что-то подобное и поместил ScrollView непосредственно в ScrollView, который местами давал сбой, и выравнивание было ужасным. И это сработало не так, как я хотел. Я вижу, что вы сделали здесь, чтобы смягчить эти побочные эффекты, и ценю ваш ответ. Еще раз спасибо!