Добавить объявление

Бекпорт onChange с oldValue для iOS 16 в SwiftUI

Всем привет! Сегодня поделюсь полезным расширением для тех, кто поддерживает iOS 16 в своих проектах. В iOS 17 компания Apple добавила улучшенную версию onChange с доступом к старому значению, но как быть, если нужно поддерживать и более ранние версии?

Решение - backport-реализация, о которой расскажу ниже:

Создадим совместимый вариант valueChanged:


public extension View {
@available(iOS, deprecated: 17.0, message: "Используйте нативный onChange в iOS 17+")
@ViewBuilder func valueChanged(
of value: V,
handler: @escaping (_ previous: V, _ current: V) -> Void
) -> some View where V: Equatable {
if #available(iOS 17, *) {
onChange(of: value, handler)
} else {
onChange(of: value) { [value] newValue in
handler(value, newValue)
}
}
}
}

Как это работает:


  • На iOS 17+ использует нативный onChange.

  • На iOS 16 эмулирует поведение через захват текущего значения.

  • Сохраняет одинаковый интерфейс для всех версий.

Добавим дополнительную возможность - отслеживание переходов:


Создадим метод для отслеживания смены конкретного значения:

public extension View {
func trackTransition(
of value: V,
from initialValue: V,
action: @escaping () -> Void
) -> some View where V: Equatable {
valueChanged(of: value) { old, new in
if old == initialValue && new != initialValue {
action()
}
}
}
}

Пример использования:


struct ContentView: View {
@State private var score: Int = 0

var body: some View {
VStack {
Text("Score: \(score)")
Button("Increase") { score += 1 }
}
.valueChanged(of: score) { old, new in
print("Score changed from \(old) to \(new)")
}
.trackTransition(of: score, from: 0) {
print("Score started changing from zero!")
}
}
}


Главное преимущество этого подхода - единый код для всех поддерживаемых версий iOS без дублирования логики.
04.08.2025 39 415