Комментарии 6
для меня как чайника в iOS было интересно, поместил в закладки.
Добавил ли эпл для нас возможность как-то отлавливать нажатия кнопки/свича, чтобы мы понимали, когда произошло нажатие для обновления ui?
То есть на примере вашего виджета: пользователь нажимает на звездочку -> мы это отлавливаем, перекрашиваем в серый -> слово успешно добавляется в избранное -> перекрашиваем звездочку в золотой, а затем через секунду обратно в начальный цвет, при неуспешной записи в красный.
Да, у вас работа завязана на UserDefaults, и в принципе по смыслу не требуется, но просто интересно знать возможно ли вообще реализовать такое поведение в новых виджетах? Тк думаю, что в задачах, где нужно обработать результат запроса в сеть, такое будет полезно
Добавил ли эпл для нас возможность как-то отлавливать нажатия кнопки/свича, чтобы мы понимали, когда произошло нажатие для обновления ui?
— Отлавливать нажатия в виджете во View в привычном смысле нельзя. Потому что каждое нажатие на Button/Toggle триггерит перерисовку всей вьюхи: в терминах виджета, один снепшот с данными перерисовывается на другой. Поэтому завязаться на булеву переменную и при нажатии на Toggle менять состояние не получится
Но Apple добавила другой способ, через который можно обработать этот случай, через задание стилей для Button/Toggle: ButtonStyle и ToggleStyle аналогично. Необходимо дописать свой стиль
На примере Toggle:
// Описываем кастомный стиль для Toggle
struct CheckToggleStyle: ToggleStyle {
var onToggleImage: (Bool) -> Image
var imageColor: Color
init(imageColor: Color,
_ onToggleImage: @escaping (Bool) -> Image) {
self.onToggleImage = onToggleImage
self.imageColor = imageColor
}
func makeBody(configuration: Configuration) -> some View {
Button {
configuration.isOn.toggle()
} label: {
Label {
self.onToggleImage(configuration.isOn)
.resizable()
.renderingMode(.template)
.foregroundColor(self.imageColor)
} icon: { }
}
.buttonStyle(.plain)
}
}
Создаём Toggle во вью. Из-за особенностей работы с виджетом, делаем с захардкоженными данными. Это позволительно, так как после нажатия на Toggle, его состояние будет всё равно меняться.
Toggle("",
isOn: false,
intent: SwipeAppIntent())
.toggleStyle(CheckToggleStyle(imageColor: .softYellow) { isOn in
Image(uiImage: (isOn ? .starFilledIcon : .starUnfilledIcon) ?? UIImage())
})
.frame(width: 30.0, height: 30.0)
И для красоты, устанавливаем задержку в 0.3 секунды в AppIntent
struct SwipeAppIntent: AppIntent {
static var title: LocalizedStringResource = "Swipe card"
static var isDiscoverable: Bool = false
func perform() async throws -> some IntentResult {
// Спим 0.3 секунды
try await Task.sleep(nanoseconds: 300_000_000)
// Продолжаем работу
let key = "group.Revolvetra.Inc.Ficha"
let storage: WordsStorageProtocol = WordsStorage(key: key)
storage.swipeCard()
return .result()
}
}
Перекрашиваем звездочку в золотой, а затем через секунду обратно в начальный цвет, при неуспешной записи в красный
— Такой случай, в предыдущем решении вряд ли можно обработать. Есть единственный вариант, в случае ошибки внутри метода perform
в AppIntent выкинуть ошибку (throw Error
), тк сигнатура метода позволяет это сделать func perform() async throws -> some IntentResult
В таком случае, виджет перезагрузится, но так как данные не поменялись, то вью моргнёт
Можно сделать, как property wrapper. Кому как нравится)
Мне показался странным синтаксис инициализации переменной с нижним подчёркиванием, обёрнутой property wrapper, поэтому сделал через обычный generic
class SomeClass {
@UserDefaultCodable var someVar: Int
init(key: String) {
self._someVar = .init(key: key, defaultValue: 0)
}
}
Пишем интерактивный виджет