Всем привет! Сегодня поделюсь полезным расширением для тех, кто поддерживает
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 без дублирования логики.