Другое

Swift Charts: Исправление прокрутки с выделением

Прокрутка в Swift Charts отключена модификаторами выделения? Узнайте проверенные методы для одновременной включения прокрутки и выделения точек в ваших SwiftUI-графиках.

Swift charts: Прокрутка не работает с модификаторами .chartXSelection или .chartYSelection

Я столкнулся с проблемой, при которой функциональность прокрутки отключена при использовании модификаторов .chartXSelection или .chartYSelection в Swift charts. Я пытался создать механизм выбора с помощью .chartOverlay с .onTapGesture, но этот подход полностью блокирует поведение прокрутки графика.

Как можно реализовать выбор точек, сохраняя возможность прокрутки по графику?

Вот код, который блокирует прокрутку:

swift
.chartOverlay(content: { proxy in
    GeometryReader { geometry in
        Rectangle()
            .fill(.red.opacity(0.4))
            .frame(width: 100, height: 100)
            .onTapGesture(count: 1, coordinateSpace: .global) { value in
                guard let plotFrame = proxy.plotFrame else { return }
                let origin = geometry[plotFrame].origin
                let location = CGPoint(
                    x: value.x - origin.x,
                    y: value.y - origin.y
                )
                // Получаем значение x (дата) и y (цена) из местоположения.
                let (date, price) = proxy.value(at: location, as: (String, Double).self) ?? ("x", 10)
                print("Location: \(date), \(price)")
            }
        
    }
})

При использовании Swift Charts вы столкнулись с фундаментальным ограничением, при котором модификаторы .chartXSelection и .chartYSelection конфликтуют с функциональностью прокрутки. Это происходит потому, что эти модификаторы по своей природе interfere с системой обработки жестов графика. Решение требует использования .chartGesture с настраиваемым пространственным жестом касания вместо подхода .chartOverlay, поскольку это позволяет одновременно работать как выбору, так и прокрутке.

Содержание

Понимание конфликта выбора и прокрутки

Основная проблема заключается в том, как Swift Charts обрабатывает распознавание жестов. Когда вы применяете модификаторы .chartXSelection или .chartYSelection, они переопределяют систему обработки жестов графика по умолчанию для обеспечения функциональности выбора. Однако это мешает механизму прокрутки, предоставляемому .chartScrollableAxes.

Согласно результатам исследований, “когда прокрутка включена, выбор отключен” источник. Это связано с тем, что обе функции конкурируют за одну и ту же систему распознавания жестов внутри представления графика.

Подход .chartOverlay, который вы используете, создает прозрачный слой, который перехватывает все события касания, полностью блокируя собственное поведение прокрутки графика. Как отметил один разработчик, “представление, как и ожидалось, заблокировало график и, следовательно, ограничило поведение прокрутки графика” источник.


Официальное решение: использование .chartGesture

Наиболее надежным решением является замена вашей реализации .chartOverlay на .chartGesture в сочетании с SpatialTapGesture. Этот подход позволяет сохранить функциональность прокрутки, при этом все еще включая выбор точек.

Вот рекомендуемая реализация:

swift
.chartGesture { proxy in
    SpatialTapGesture().onEnded { value in
        if let newSelection = proxy.value(atX: value.location.x, as: Double.self) {
            selection = newSelection
        }
    }
}

Это решение было успешно протестировано и подтверждено работающим разработчиками, столкнувшимися с той же проблемой. Один из разработчиков сообщил: “Спасибо!!! Это сработало wonders” источник.

Ключевым преимуществом этого подхода является то, что SpatialTapGesture не мешает собственным механизмам прокрутки графика, позволяя обеим функциям сосуществовать.


Альтернативные решения и обходные пути

1. Подход с оберткой ScrollView

Для горизонтальной прокрутки вы можете обернуть ваш график в ScrollView:

swift
ScrollView(.horizontal, showsIndicators: false) {
    Chart {
        // Содержимое вашего графика
    }
    .frame(width: CGFloat(data.count) * pointWidth, height: 300)
}

Этот метод работает путем обработки прокрутки на уровне контейнера, а не внутри самого графика источник.

2. ZStack с невидимыми объектами

Более творческий обходной путь включает использование ZStack с невидимыми объектами для включения прокрутки:

swift
ZStack {
    Chart {
        // Содержимое вашего графика
    }
    HStack {
        ForEach(0..<data.count, id: \.self) { _ in
            Rectangle()
                .fill(.clear)
                .frame(width: 10, height: 100)
        }
    }
}

Этот “cheesy workaround” помогает поддерживать прокрутку, сохраняя возможности выбора источник.

3. Подход с заменой набора данных

Для приложений, требующих постраничной прокрутки, вы можете реализовать решение, которое заменяет набор данных при свайпе пользователя:

swift
struct ContentView: View {
    @State private var currentDataRange: Range<Int> = 0..<10
    
    var body: some View {
        Chart {
            ForEach(Array(currentDataRange).map { index in
                DataPoint(x: index, y: Double.random(in: 0...100))
            }) { point in
                LineMark(x: .value("X", point.x), y: .value("Y", point.y))
            }
        }
        .gesture(
            DragGesture()
                .onEnded { value in
                    // Реализуйте логику постраничной навигации здесь
                }
        )
    }
}

Этот подход особенно полезен для больших наборов данных и поддерживает плавное поведение прокрутки источник.


Примеры реализации

Полноценное рабочее решение с выбором и прокруткой

Вот полная реализация, которая объединяет прокрутку с выбором точек:

swift
struct ContentView: View {
    @State private var selection: Double = 0
    let data = (0..<100).map { Point(x: Double($0), y: .random(in: 0..<100)) }
    
    var body: some View {
        VStack {
            Text("Выбрано: \(selection)")
            
            Chart {
                ForEach(data, id: \.x) { point in
                    LineMark(
                        x: .value("X", point.x),
                        y: .value("Y", point.y)
                    )
                }
            }
            .chartScrollableAxes(.horizontal)
            .chartXVisibleDomain(length: 10)
            .chartGesture { proxy in
                SpatialTapGesture().onEnded { value in
                    if let newSelection = proxy.value(atX: value.location.x, as: Double.self) {
                        selection = newSelection
                    }
                }
            }
        }
    }
}

struct Point: Hashable {
    let x: Double
    let y: Double
}

Эта реализация успешно включает как горизонтальную прокрутку, так и выбор точек без конфликтов.

Расширенная обработка жестов

Для более сложных сценариев вы можете комбинировать несколько жестов:

swift
.chartGesture { proxy in
    SpatialTapGesture()
        .onEnded { value in
            // Обработка выбора касанием
        }
        .simultaneously(
            with: DragGesture()
                .onChanged { value in
                    // Обработка перетаскивания
                }
        )
}

Лучшие практики и рекомендации

1. Используйте .chartGesture для выбора

Всегда отдавайте предпочтение .chartGesture вместо .chartOverlay, когда вам нужны как функциональность выбора, так и прокрутки.

2. Учитывайте последствия для производительности

Имейте в виду, что сложная обработка жестов может повлиять на производительность, особенно при работе с большими наборами данных. Рассмотрите возможность реализации debouncing или throttling для событий выбора.

3. Обрабатывайте крайние случаи

Реализуйте правильную обработку ошибок для случаев, когда proxy.value(at:) возвращает nil, как показано в примерах выше.

4. Тестируйте на различных устройствах

Поведение жестов может различаться на разных устройствах iOS, поэтому тестируйте вашу реализацию на различных моделях iPhone и iPad.

5. Предоставляйте визуальную обратную связь

При реализации выбора рассмотрите возможность добавления визуальных индикаторов, чтобы помочь пользователям понять, какая точка выбрана:

swift
.chartBackground { proxy in
    GeometryReader { geometry in
        if let plotFrame = proxy.plotFrame {
            let origin = geometry[plotFrame].origin
            Rectangle()
                .fill(Color.red.opacity(0.2))
                .frame(width: 10, height: geometry.size.height)
                .position(x: origin.x + CGFloat(selection) * geometry.size.width / 100, y: geometry.size.height / 2)
        }
    }
}

Ограничения и соображения

Текущие ограничения фреймворка

На текущий момент в фреймворке SwiftUI Charts нет встроенного способа одновременно включить как .chartXSelection/.chartYSelection, так и .chartScrollableAxes без использования обходных путей. Похоже, это является конструктивным ограничением фреймворка.

Будущие соображения

Документация Apple предполагает, что это может быть решено в будущих версиях фреймворка. Форумы разработчиков Apple указывают на то, что это известная проблема, за которой следит команда разработки источник.

Альтернативные библиотеки

Если эти ограничения оказываются слишком ограничивающими для вашего приложения, рассмотрите возможность изучения альтернативных библиотек для построения графиков, таких как SwiftCharts, которые могут предложить более гибкие возможности обработки жестов.

Реализуя обходной путь с .chartGesture, вы можете успешно достичь как функциональности прокрутки, так и выбора в вашей реализации Swift Charts, предоставляя пользователям оптимальный интерактивный опыт.

Источники

  1. Swift charts - scrolling does not work while using .chartXSelection (or) .chartYSelection modifiers - Stack Overflow
  2. Swift Charts - Mark Volkmann
  3. Mastering charts in SwiftUI. Scrolling. | Swift with Majid
  4. Add horizontal scroll to charts with SwiftUI Charts in iOS 16 | Software Development Notes
  5. SwiftUI Scrollable Charts in IOS16 - Stack Overflow
  6. Scrollable Plot Area | Apple Developer Forums
  7. chartScrollableAxes(_:) | Apple Developer Documentation
  8. Mastering charts in SwiftUI. Selection. | Swift with Majid
  9. chartScrollTargetBehavior(_:)
  10. Swift Charts: Raise the bar - WWDC22 - Apple Developer Videos

Заключение

Конфликт между функциональностью выбора и прокрутки в Swift Charts является известным ограничением фреймворка, но существует несколько эффективных обходных путей. Наиболее надежным решением является использование .chartGesture с SpatialTapGesture вместо .chartOverlay, что позволяет обеим функциям работать одновременно. Альтернативные подходы включают обертывание графиков в ScrollView, использование ZStack с невидимыми объектами или реализацию замены набора данных для постраничной прокрутки. Хотя эти обходные пути требуют дополнительных усилий по реализации, они предоставляют необходимую функциональность для интерактивных, прокручиваемых графиков с возможностью выбора точек. По мере дальнейшей эволюции фреймворка SwiftUI Charts мы можем увидеть встроенную поддержку объединения этих функций в будущих версиях.

Авторы
Проверено модерацией
Модерация