Pull to refresh

Comments 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()
    }
}

Toggle кнопки избранное
Toggle кнопки избранное

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

— Такой случай, в предыдущем решении вряд ли можно обработать. Есть единственный вариант, в случае ошибки внутри метода perform в AppIntent выкинуть ошибку (throw Error), тк сигнатура метода позволяет это сделать func perform() async throws -> some IntentResult
В таком случае, виджет перезагрузится, но так как данные не поменялись, то вью моргнёт

Спасибо за статью и подробный ответ)
Будет интересно почитать от вас про динамичный конфиг)

UFO just landed and posted this here

Можно сделать, как property wrapper. Кому как нравится)
Мне показался странным синтаксис инициализации переменной с нижним подчёркиванием, обёрнутой property wrapper, поэтому сделал через обычный generic

class SomeClass {
    
    @UserDefaultCodable var someVar: Int
    
    init(key: String) {
        self._someVar = .init(key: key, defaultValue: 0)
    }
}

Sign up to leave a comment.