Блокировка/устранение прокрутки Наблюдение за клавишей предпочтений в SwiftUIIOS

Программируем под IOS
Ответить
Anonymous
 Блокировка/устранение прокрутки Наблюдение за клавишей предпочтений в SwiftUI

Сообщение Anonymous »

У меня есть SwiftUI ScrollView, для которого я хочу выполнять определенные действия при прокрутке, в частности при определении направления прокрутки.
My ScrollView, который SwiftUI находится в стеке навигации на основе UIKit.
Моя цель — определить направление прокрутки и, соответственно, скрыть панель навигации и панель вкладок при прокрутке вниз и сделайте обратное при прокрутке вверх.
Вот минимальный воспроизводимый пример:
В моем делегате приложения я настроил вышеуказанный стек навигации следующим образом:

Код: Выделить всё

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Create the SwiftUI view that provides the window contents.

let tabBarViewController = UITabBarController()
tabBarViewController.title = "Scroll Hide"

let navigationController = UINavigationController(rootViewController: tabBarViewController)

let contentView = ContentView()
.didChangeScrollDirection { scrollDirection in

let shouldHide = scrollDirection == .down

UIView.animate(withDuration: 0.25, delay: 0, options: .curveEaseInOut) {
navigationController.setNavigationBarHidden(shouldHide, animated: true)

if let navControllerView = navigationController.view {
let tabBarFrame = tabBarViewController.tabBar.frame

if shouldHide {
tabBarViewController.tabBar.frame.origin.y = navControllerView.frame.maxY + tabBarFrame.height
} else {
tabBarViewController.tabBar.frame.origin.y = navControllerView.frame.maxY - tabBarFrame.height
}

navControllerView.layoutIfNeeded()
}
}
}

let vc = UIHostingController(rootView: contentView)
vc.title = "Home"

tabBarViewController.viewControllers = [vc]

let window = UIWindow(frame: UIScreen.main.bounds)
window.rootViewController = navigationController
self.window = window
window.makeKeyAndVisible()

return true
}
}
И это представление, которое наблюдает за прокруткой представления прокрутки, а затем пытается опубликовать это подписчику выше для выполнения анимации.

Код: Выделить всё

struct ContentView: View {
private let minimumDirectionChangeOffset = 16.0

private var scrollDirectionUpdateHandler: ((ScrollDirection) -> Void)?

@State private var currentDirection = ScrollDirection.none
@State private var previousScrollOffset = CGFloat.zero

var body: some View {
ScrollView {
Rectangle()
.frame(height: 10000)
.frame(maxWidth: .infinity)
.foregroundStyle(.blue)
.background(scrollDirectionTrackerView)
.onPreferenceChange(ScrollOffsetKey.self) { updatedOffset in
updateScrollDirection(updatedOffset)
}
}
.coordinateSpace(name: "scrollView")
.navigationTitle("Hello")
}

private var scrollDirectionTrackerView: some View {
GeometryReader { proxy in
Color.clear.preference(key: ScrollOffsetKey.self,
value: -proxy.frame(in: .named("scrollView")).origin.y)
}
}

func didChangeScrollDirection(_ handler: @escaping (ScrollDirection) -> Void) -> ContentView {
var newView = self
newView.scrollDirectionUpdateHandler = handler
return newView
}

private func updateScrollDirection(_ newOffset: CGFloat) {
let offsetDifference = previousScrollOffset - newOffset
var updatedDirection = currentDirection

if abs(offsetDifference) > minimumDirectionChangeOffset {
if offsetDifference >  0 {
updatedDirection = .up
} else {
updatedDirection = .down
}

previousScrollOffset = newOffset
}

// Only publish if we have a different direction
if updatedDirection != currentDirection {
print("previous offset: \(previousScrollOffset)")
print("updated direction: \(updatedDirection)")
currentDirection = updatedDirection
scrollDirectionUpdateHandler?(currentDirection)
}
}
}

enum ScrollDirection {
case up
case down
case forward
case backward
case none
}

private struct ScrollOffsetKey: PreferenceKey {
static var defaultValue = CGFloat.zero

static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
По большей части это работает хорошо.
Я хочу обратить ваше внимание на строки во втором фрагменте выше, где я говорю // Только опубликовать, если у нас другое направление
Если я выполняю быструю прокрутку в любом направлении, это работает хорошо.
Изображение

Однако, когда я перетаскиваю очень медленно, я чувствую, что либо возникает состояние гонки, когда несколько потоков обращаются к этому функция или что-то еще, но направление задается несколько раз, что приводит к запуску нескольких анимаций, что приводит к прерывистому пользовательскому интерфейсу из-за выполняемой анимации.
Изображение

Мне интересно, есть ли ошибка в моей реализации или есть более элегантный способ справиться с этим:
  • с каким-то устранением дребезга/регулирования
  • каким-то семафором/блокировкой, чтобы мы могли заблокировать функцию
  • или лучше даже математика?
Некоторые другие идеи, которые пришли в голову, - это переменная Bool для блокировки функции, но я надеялся на что-то более элегантное.
Я попробовал этот старый ответ, но он мне не помог.
Есть ли лучший способ предотвратить многократное изменение направления прокрутки при медленном перетаскивании?
Наконец, мне нужна поддержка iOS 16+.

Подробнее здесь: https://stackoverflow.com/questions/793 ... in-swiftui
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «IOS»