Есть ли способ установить идентификатор представления после перехода анимации?

В 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)
        }
    }
}
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
0
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

В 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 в качестве родительского контейнера для дочерних представлений.
  • Таким образом, нет необходимости в двух версиях кадра 2. Однако для этого требуется, чтобы текстовое сообщение, отображаемое под прокручиваемым содержимым, было перемещено в родительское представление.
  • Чтобы запретить пользователю прокручивать обратно к кадру 1, прокрутку можно отключить после прокрутки кадра 1.
  • Привязку к 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!")
            }
        }
    }
}

Animation

большое спасибо! Да, последний вид и вообще нижний текст были просто заполнителями. Это работает отлично и именно так, как я себе представлял. У меня есть более ранняя версия (вероятно, TestSwiftUIView3), где я попытался сделать что-то подобное и поместил ScrollView непосредственно в ScrollView, который местами давал сбой, и выравнивание было ужасным. И это сработало не так, как я хотел. Я вижу, что вы сделали здесь, чтобы смягчить эти побочные эффекты, и ценю ваш ответ. Еще раз спасибо!

nilgirian 22.07.2024 01:51

Другие вопросы по теме