Не работает isSpecialTransitionNeeded в SwiftUI

Вопросы и ответы

Код: В этом коде, когда я помещаю GeometryReader внутрь ZStack, анимация isSpecialTransitionNeeded не работает. Раньше она работала. Почему? Как сделать так, чтобы она работала? Пожалуйста, дайте совет.

struct DashboardBottomView: View {
@State private var stringArray = [String]()
@State private var xOffset = CGFloat.zero
@GestureState private var dragOffset = CGFloat.zero
@State private var selectedTab: String?
let backgroundHeight: CGFloat = 90
let labelSpacing: CGFloat = 27

@State private var previousTab: String?
@State private var selectedIndexTab: Int? = nil

@ViewBuilder
private func viewForSelectedTab() -> some View {
    switch selectedTab {
    case "Dashboard":
        DashboardView(viewModel: viewModel)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    case "Attendance":
        AttendanceSwipeView(viewModel: viewModel)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    case "Feed":
        DashboardFeedView()
            .frame(maxWidth: .infinity, maxHeight: .infinity)
    default:
        DashboardView(viewModel: viewModel)

            .frame(maxWidth: .infinity, maxHeight: .infinity)
    }
}

private var isSpecialTransitionNeeded: Bool {
    if let previousTab {
        return (previousTab == "Feed" && selectedTab == "Attendance") ||
        (previousTab == "Attendance" && selectedTab == "Dashboard")
    } else {
        return false
    }
}

var body: some View {

    VStack(spacing: 0) {
        Spacer()

        ZStack {

            if isSpecialTransitionNeeded {
                viewForSelectedTab()
                    .transition(
                        .asymmetric(
                            insertion: .swipeMovement,
                            removal: .opacity
                        )
                    )
            } else {
                viewForSelectedTab()
                    .transition(.opacity)
            }
        }
        .background(.gray)

        .animation(.easeInOut(duration: 1), value: selectedTab)
        .toolbar(.hidden, for: .navigationBar)

        ZStack{

            Image("Vector 19")
                .resizable()
                .scaledToFill() // Настройте масштабирование по мере необходимости
                .edgesIgnoringSafeArea(.all)

            GeometryReader { outer in
                let w = outer.size.width
                let halfWidth = w / 2
                let curveHeight = backgroundHeight * 0.1
                let slopeLen = sqrt((halfWidth * halfWidth) + (curveHeight * curveHeight))
                let arcRadius = (slopeLen * slopeLen) / (2 * curveHeight)
                let arcAngle = 4 * asin((slopeLen / 2) / arcRadius)

                let totalItemsWidth = CGFloat(stringArray.count - 1) * labelSpacing
                let needsScrolling = totalItemsWidth > w
                let startOffset = needsScrolling ? 0 : (w - totalItemsWidth) / 2

                HStack(spacing: labelSpacing) {
                    ForEach(Array(stringArray.enumerated()), id: \.offset) { index, item in
                        Text(item)
                            .lineLimit(1)
                            .fixedSize()
                            .hidden()
                            .onTapGesture {
                                previousTab = selectedTab // Сохранить предыдущую вкладку
                                selectedTab = item // Обновить выбранную вкладку
                                print("Выбрано элемент \(item) с индексом \(index)")
                            }
                            .overlay {
                                GeometryReader { proxy in
                                    let midX = proxy.frame(in: .global).midX
                                    let offsetFromMiddle = midX - (w / 2)

                                    CurvedText(string: item, radius: arcRadius)
                                        .foregroundStyle(.white)
                                        .offset(y: -arcRadius)
                                        .rotationEffect(.radians((offsetFromMiddle / w) * arcAngle))
                                        .offset(x: -offsetFromMiddle, y: arcRadius)
                                        .onAppear {
                                            if selectedTab == item && xOffset == .zero {
                                                xOffset = -offsetFromMiddle
                                            }
                                        }

                                        .onTapGesture {
                                            previousTab = selectedTab
                                            selectedTab = item
                                            print("Выбрано элемент \(item) с индексом \(index)")

                                            let itemMidX = proxy.frame(in: .global).midX
                                            let offsetFromMiddle = itemMidX - (w / 2)

                                            withAnimation(.easeInOut) {
                                                xOffset -= offsetFromMiddle
                                            }
                                        }

                                        .onChange(of: dragOffset) { newVal in
                                            if newVal == 0 {
                                                let halfWidth = (proxy.size.width + labelSpacing) / 2
                                                if (abs(offsetFromMiddle) < halfWidth) ||
                                                    (index == 0 && offsetFromMiddle > 0) ||
                                                    (index == stringArray.count - 1 && offsetFromMiddle < 0) {
                                                    previousTab = selectedTab
                                                    selectedTab = item
                                                    //                                                    print("selectedTab... \(selectedTab)")
                                                    xOffset -= offsetFromMiddle
                                                }
                                            }
                                        }
                                }
                            }
                    }
                }
                .offset(x: xOffset + dragOffset + startOffset)
                .animation(.easeInOut, value: xOffset)
                .gesture(
                    DragGesture(minimumDistance: 0)
                        .updating($dragOffset) { val, state, trans in
                            state = val.translation.width
                        }
                        .onEnded { val in
                            xOffset += val.translation.width
                        }
                )
                .padding(.top, curveHeight + 26)
                .frame(width: w)

            }

            .padding(.top, -25)
            tabViewForDashboard(selectedIndex: $selectedIndexTab, showSideMenu: $showSideMenu)

        }
        .frame(height: 150)
        .padding(.top, -30)            
    }
}
}

Ответ

Когда вы добавляете GeometryReader внутри ZStack в вашем коде SwiftUI и анимация isSpecialTransitionNeeded перестает работать, это связано с тем, как View обновляются в SwiftUI и как они обрабатывают анимации. Вот как можно решить эту проблему:

Причины проблемы:

  1. Изменение состояния: Когда вы оборачиваете ваш контент в GeometryReader, он может влиять на обновление представления и анимации. В частности, изменения, происходящие в GeometryReader, могут вызывать перерисовку всего представления и, как следствие, сбивать анимации.

  2. Отсутствие четкого update: SwiftUI не всегда может отследить каждое изменение состояния, если у вас есть несколько зависимостей и вложенные виджеты, особенно если используется GeometryReader.

Решение:

Чтобы исправить проблему с анимацией, можно использовать следующее:

  1. Создайте отдельные представления: Вместо того чтобы помещать все ваши представления внутри GeometryReader, вынесите его логику в отдельные представления. Это поможет SwiftUI легче отслеживать изменения состояния.

  2. Используйте withAnimation: Если вы явно модифицируете состояние, которое влияет на отображение переходов, убедитесь, что вы используете withAnimation при изменении selectedTab.

Вот пример, как можно реорганизовать ваш код:

struct DashboardBottomView: View {
    @State private var stringArray = [String]()
    @State private var xOffset = CGFloat.zero
    @GestureState private var dragOffset = CGFloat.zero
    @State private var selectedTab: String?

    let backgroundHeight: CGFloat = 90
    let labelSpacing: CGFloat = 27

    @State private var previousTab: String?
    @State private var selectedIndexTab: Int? = nil

    private var isSpecialTransitionNeeded: Bool {
        if let previousTab = previousTab {
            return (previousTab == "Feed" && selectedTab == "Attendance") ||
                   (previousTab == "Attendance" && selectedTab == "Dashboard")
        }
        return false
    }

    var body: some View {
        VStack(spacing: 0) {
            Spacer()

            ZStack {
                if isSpecialTransitionNeeded {
                    viewForSelectedTab()
                        .transition(
                            .asymmetric(
                                insertion: .swipeMovement,
                                removal: .opacity
                            )
                        )
                } else {
                    viewForSelectedTab()
                        .transition(.opacity)
                }
            }
            .background(.gray)
            .animation(.easeInOut(duration: 1), value: selectedTab)
            .toolbar(.hidden, for: .navigationBar)

            tabScrollView
                .frame(height: 150)
        }
    }

    @ViewBuilder
    private func viewForSelectedTab() -> some View {
        switch selectedTab {
        case "Dashboard":
            DashboardView(viewModel: viewModel)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        case "Attendance":
            AttendanceSwipeView(viewModel: viewModel)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        case "Feed":
            DashboardFeedView()
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        default:
            DashboardView(viewModel: viewModel)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
        }
    }

    private var tabScrollView: some View {
        ZStack {
            Image("Vector 19")
                .resizable()
                .scaledToFill()
                .edgesIgnoringSafeArea(.all)

            GeometryReader { outer in
                let w = outer.size.width
                let totalItemsWidth = CGFloat(stringArray.count - 1) * labelSpacing
                let needsScrolling = totalItemsWidth > w
                let startOffset = needsScrolling ? 0 : (w - totalItemsWidth) / 2

                HStack(spacing: labelSpacing) {
                    ForEach(Array(stringArray.enumerated()), id: \.offset) { index, item in
                        Text(item)
                            .lineLimit(1)
                            .fixedSize()
                            .hidden()
                            .onTapGesture {
                                withAnimation {
                                    previousTab = selectedTab
                                    selectedTab = item
                                }
                            }
                    }
                }
                .offset(x: xOffset + dragOffset + startOffset)
                .animation(.easeInOut, value: xOffset)
                .gesture(
                    DragGesture(minimumDistance: 0)
                        .updating($dragOffset) { val, state, trans in
                            state = val.translation.width
                        }
                        .onEnded { val in
                            xOffset += val.translation.width
                        }
                )
            }
        }
    }
}

Заключение

Этот код обеспечивает лучшую организацию вашего представления и может помочь вернуть анимацию isSpecialTransitionNeeded. Отключение GeometryReader от непосредственного взаимодействия с состоянием анимации часто помогает избежать нежелательных эффектов при работе с данными или изменениях состояния.

Оцените материал
Добавить комментарий

Капча загружается...