Ошибка iOS 26 UISlider: valueChanged и allTouches пустые
Решение проблемы с UISlider в iOS 26, когда valueChanged возвращает пустое событие и allTouches равно nil. Обходные пути для определения фаз перетаскивания.
В iOS 26 событие .valueChanged UISlider всегда пустое, а allTouches всегда равно nil, невозможно определить конец перетаскивания слайдера
У меня есть приложение, которое работало нормально до iOS 26. В моем приложении я использую событие .valueChanged UISlider для получения фазы и выполнения различных действий в зависимости от того, является ли она .began, .moved или .ended.
Это распространенное решение для определения фаз взаимодействия со слайдером. Ниже приведен мой простой пример:
import UIKit
import SnapKit
class ViewController: UIViewController {
let label = UILabel()
let slider = UISlider()
override func viewDidLoad() {
super.viewDidLoad()
let stackView = UIStackView(arrangedSubviews: [label, slider])
stackView.axis = .vertical
stackView.spacing = 20
view.addSubview(stackView)
stackView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.horizontalEdges.equalToSuperview().inset(30)
}
label.textAlignment = .center
slider.isContinuous = true
slider.minimumValue = 0
slider.maximumValue = 100
slider.addTarget(self, action: #selector(sliderValueChanged(sender:event:)), for: .valueChanged)
}
@objc func sliderValueChanged(sender: UISlider, event: UIEvent) {
print("Слайдер: (slider.value), (event)")
label.text = "Значение: (sender.value)"
switch event.allTouches?.first?.phase {
case .began:
print("начато")
case .moved:
print("перемещено")
case .ended:
print("завершено")
case .cancelled:
print("отменено")
default:
break
}
}
}
Ранее это работало нормально. Но в iOS 26 событие, передаваемое в мою функцию sliderValueChanged, проблематично. Оно не равно nil, но также не является корректным событием. Например, операция print просто выводит пустой текст для события. А установка точки останова показывает его как пустой объект события.
Из-за этого в iOS 26 event.allTouches всегда равно nil. Поэтому я не могу определить, какая фаза события.
Это ошибка? Как можно обойти эту проблему?
Я обнаружил, что если я переключаюсь с .valueChanged на .allEvents, то получаю событие для фаз began и moved. Однако я не получаю фазы ended или cancelled.
Как получить и эти фазы?
iOS 26 содержит известную ошибку в обработке событий UISlider, когда событие .valueChanged всегда пустое и allTouches равно nil, что мешает определению фазы перетаскивания. Существуют обходные решения, включая использование .allEvents для фаз began и moved, а также подклассирование UISlider для отслеживания всех фаз включая ended и cancelled.
Содержание
- Описание проблемы: ошибка iOS 26 UISlider
- Почему возникает ошибка и как она проявляется
- Обходной путь 1: Использование .allEvents
- Обходной путь 2: Подклассирование UISlider
- Рекомендации и лучшие практики
- Источники
- Заключение
Описание проблемы: ошибка iOS 26 UISlider
В iOS 26 разработчики столкнулись с серьезной проблемой при работе с элементом управления UISlider. Событие .valueChanged, которое ранее корректно передавало объект UIEvent с информацией о прикосновениях, теперь возвращает пустое событие. Основная проблема заключается в том, что свойство event.allTouches всегда равно nil, что делает невозможным определение фазы взаимодействия со слайдером (.began, .moved, .ended или .cancelled).
Это особенно критично для приложений, которые должны реагировать на разные фазы перетаскивания, например, чтобы отображать подсказки во время перемещения или выполнять действия только после завершения перетаскивания.
Из обсуждений на Stack Overflow известно, что эта проблема присутствует во всех бета-версиях iOS 26 и официально подтверждена Apple. Разработчикам приходится искать обходные пути, чтобы сохранить функциональность своих приложений.
Почему возникает ошибка и как она проявляется
Причина этой ошибки до сих пор не разглашена Apple, но из анализа сообщений разработчиков можно сделать вывод, что в iOS 26 произошли изменения в механизме обработки событий для UISlider. Вместо того чтобы передавать полный объект UIEvent в обработчик .valueChanged, система теперь возвращает неполный или пустой объект.
Основные проявления проблемы:
-
Пустое событие: Объект
UIEvent, передаваемый в обработчик, не содержит информации о прикосновениях. При попытке вывести его в консоль отображается пустой объект. -
nil в allTouches: Свойство
allTouchesсобытия всегда равноnil, из-за чего невозможно определить фазу взаимодействия. -
Отсутствие фаз ended/cancelled: При переключении на
.allEventsудается получить события для фаз.beganи.moved, но фазы.endedи.cancelledне генерируются.
Эта проблема затрагивает все приложения, использующие стандартный подход для определения фаз перетаскивания UISlider, как показано в примере кода пользователя. Код, который работал безупречно в предыдущих версиях iOS, теперь оказывается нерабочим в iOS 26.
На официальных форумах Apple разработчики могут отслеживать статус этой ошибки (Feedback ID: FB18581605), но обходные пути уже предлагаются сообществом.
Обходной путь 1: Использование .allEvents
Первый и самый простой обходной путь — переключиться с .valueChanged на .allEvents. Этот подход позволяет получать события для фаз .began и .moved, но не решает проблему с отсутствием фаз .ended и .cancelled.
Вот как можно модифицировать код:
slider.addTarget(self, action: #selector(sliderValueChanged(sender:event:)), for: .allEvents)
@objc func sliderValueChanged(sender: UISlider, event: UIEvent) {
label.text = "Значение: (sender.value)"
if let touchEvent = event.allTouches?.first {
switch touchEvent.phase {
case .began:
print("начато")
case .moved:
print("перемещено")
case .ended, .cancelled:
// В iOS 26 эти фазы не генерируются для .allEvents
print("завершено или отменено")
default:
break
}
}
}
Преимущества этого подхода:
- Простота реализации — требуется минимальные изменения в коде
- Сохраняет функциональность для фаз
.beganи.moved
Недостатки:
- Не решает проблему с отсутствием фаз
.endedи.cancelled - Может генерировать больше событий, чем необходимо, что влияет на производительность
Этот метод подходит, если вашему приложению достаточно знать о начале и процессе перетаскивания, а точное завершение не критично. Однако для многих приложений отсутствие информации о завершении перетаскивания делает этот метод неполноценным решением.
Обходной путь 2: Подклассирование UISlider
Более надежным решением является подклассирование UISlider и переопределение методов жизненного цикла прикосновений. Этот подход позволяет отслеживать все фазы взаимодействия, включая .ended и .cancelled, независимо от ошибки в iOS 26.
Вот пример реализации:
class CustomSlider: UISlider {
override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
print("начато")
return super.beginTracking(touch, with: event)
}
override func continueTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
print("перемещено")
return super.continueTracking(touch, with: event)
}
override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
print("завершено")
super.endTracking(touch, with: event)
}
override func cancelTracking(with event: UIEvent?) {
print("отменено")
super.cancelTracking(with: event)
}
}
Затем используйте CustomSlider вместо стандартного UISlider:
let slider = CustomSlider()
slider.addTarget(self, action: #selector(sliderValueChanged), for: .valueChanged)
@objc func sliderValueChanged(sender: UISlider) {
label.text = "Значение: (sender.value)"
}
Преимущества этого подхода:
- Позволяет отслеживать все фазы взаимодействия
- Не зависит от состояния
UIEventиallTouches - Сохраняет функциональность
.valueChangedдля обновления значения
Недостатки:
- Требует создания отдельного класса-подкласса
- Небольшое увеличение объема кода
Этот метод является предпочтительным для приложений, которым критически важно знать о всех фазах взаимодействия со слайдером. Он полностью обходит проблему с UIEvent в iOS 26, так как не полагается на него для определения фаз.
Дополнительные детали о подклассировании UISlider можно найти в статье Improving UISlider’s Precision, хотя она не посвящена напрямую этой ошибке, но демонстрирует преимущества подхода.
Рекомендации и лучшие практики
При работе с UISlider в iOS 26 придерживайтесь следующих рекомендаций:
- Проверьте версию iOS: Добавьте проверку версии iOS перед выбором подхода к обработке событий:
if #available(iOS 16.0, *) {
// Код для iOS 16 и новее
} else {
// Код для более старых версий
}
-
Используйте подклассирование для критически важных функций: Если ваше приложение зависит от точного определения всех фаз перетаскивания, используйте подход с подклассированием UISlider.
-
Реализуйте резервный механизм: Для приложений, поддерживающих несколько версий iOS, реализуйте оба метода и выбирайте в зависимости от версии системы.
-
Следите за обновлениями Apple: Эта ошибка может быть исправлена в будущих обновлениях iOS. Проверяйте документацию Apple и Feedback Assistant для обновлений.
-
Оптимизируйте производительность: При использовании
.allEventsубедитесь, что обработка событий не создает избыточную нагрузку на систему. -
Тестируйте thoroughly: Проверьте работу слайдера на всех целевых устройствах и версиях iOS, особенно на границах версий (iOS 15.5 vs iOS 26).
-
Рассмотрите альтернативные UI элементы: Если проблема критична и обходные пути не подходят, рассмотрите возможность использования других элементов управления, таких как
UIStepperили кастомных view.
Следование этим рекомендациям поможет минимизировать влияние ошибки iOS 26 на ваше приложение и обеспечить плавный пользовательский опыт.
Источники
-
Stack Overflow: iOS 26 UISlider’s .valueChanged event is always blank - Подробное обсуждение проблемы и возможных решений.
-
Stack Overflow: How to detect the end of slider drag? - Пример кода для определения фаз взаимодействия со слайдером в Swift.
-
Improving UISlider’s Precision – Arek Holko - Статья о подклассировании UISlider для улучшения точности.
-
Apple Developer Forums: Bug Reporting - Официальная информация о системе отслеживания ошибок в Apple.
-
Apple Developer Forums: UIKit - Форум для обсуждения вопросов, связанных с UIKit, включая UISlider.
Заключение
Ошибка iOS 26 с UISlider, делающая невозможным определение фаз взаимодействия через .valueChanged, представляет серьезную проблему для разработчиков. Однако благодаря активному сообществу и предложенным обходным путям, функциональность можно восстановить.
Основные решения включают:
- Переход на
.allEventsдля получения фаз.beganи.moved - Подклассирование UISlider для полного контроля над всеми фазами взаимодействия
Рекомендуется использовать второй подход для приложений, которым критически важно знать о завершении перетаскивания. Также важно следить за обновлениями Apple, так как эта ошибка может быть исправлена в будущих версиях iOS.
Внедрение правильного решения позволит сохранить качество пользовательского опыта и избежать проблем с совместимостью вашего приложения в iOS 26 и последующих версиях.