3

I have a list in SwiftUI View. Which is in UIHostingController. I want to hide or show UINavigationBar on the basis of the Scroll Direction of the List. This UINavigationBar is in UIkit in UIHostingController. I tried adding DragGesture but it doesn't give continuous updates on the Scroll direction.

override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if scrollView.panGestureRecognizer.translation(in: scrollView).y < 0 {
            navigationController?.setNavigationBarHidden(true, animated: true)
        } else {
            navigationController?.setNavigationBarHidden(false, animated: true)
        }
    }

Basically, I need a replacement of the above code in SwiftUI.

Please don't suggest LazyList or any solution related to iOS 14, as my min iOS target is iOS 13.

Prathamesh
  • 140
  • 1
  • 2
  • 15
  • add some additional info, see this may be it helps you : [How to make a SwiftUI List scroll automatically?](https://stackoverflow.com/questions/57258846/how-to-make-a-swiftui-list-scroll-automatically) – Anbu.Karthik Aug 31 '21 at 14:14
  • Is there any reason that you use UIKit? How about using custom ScrollView in pure SwiftUI? – mahan Aug 31 '21 at 15:24
  • @mahan I am using uikit just for UInavigationBar, just because we have common navigation bar across whole app. I just need to know scroll direction of list in Swiftui itself. – Prathamesh Sep 02 '21 at 19:16
  • Does this answer your question https://stackoverflow.com/a/63216812/12299030? – Asperi Sep 07 '21 at 12:53

3 Answers3

3

You can create ScrollViewReader observing preference key changes.

struct DemoScrollViewOffsetView: View {

  @State private var offset = CGFloat.zero

  var body: some View {
      ScrollView {
          VStack {
              ForEach(0..<100) { i in
                  Text("Item \(i)").padding()
              }
          }.background(GeometryReader {
              Color.clear.preference(key: ViewOffsetKey.self,
                                   value: -$0.frame(in: .named("scroll")).origin.y)
          }).onPreferenceChange(ViewOffsetKey.self) { print("offset >> \($0)") }
      }.coordinateSpace(name: "scroll")
  } } 

struct ViewOffsetKey: PreferenceKey {
    typealias Value = CGFloat
    static var defaultValue = CGFloat.zero
    static func reduce(value: inout Value, nextValue: () -> Value) {
        value += nextValue()
    }
}

This will read your content offset and print it.

Dharman
  • 30,962
  • 25
  • 85
  • 135
kchopda
  • 312
  • 4
  • 16
  • I know this, but how will I determine scroll direction with the offset. – Prathamesh Sep 09 '21 at 10:48
  • In your case offset will be equivalent to `scrollView.panGestureRecognizer.translation(in: scrollView).y` – kchopda Sep 09 '21 at 13:32
  • No, not really because translation and offset are different. I have to do some workaround on the above logic. I did find the solution. But because of using geometryreader scrollview becomes laggy. Need to find solution to it. – Prathamesh Sep 13 '21 at 07:08
  • @Prathamesh can you share the solution you did using geometryReader? May be I can help with lag. – kchopda Sep 16 '21 at 13:58
0

I think the easiest way to solve this problem is to use Introspect.

You can observe the offset of the contents of the table view with KVO, there is no need to inherit.

I tried animating navigationBarHidden with the State variable, but it somehow changes without animation. The problem is similar to this issue, but the suggested solution didn't work for me, so I had to do it with the introspected navigation controller.

I moved this logic to the modifier:

import SwiftUI
import Introspect

extension View {
    func hideNavigationBarOnScroll() -> some View {
        modifier(HideNavigationBarOnScrollModifier())
    }
}

private struct HideNavigationBarOnScrollModifier: ViewModifier {
    @State
    var observation: NSKeyValueObservation?

    @State
    var navigationController: UINavigationController?
    
    func body(content: Content) -> some View {
        content
            .introspectNavigationController {
                navigationController = $0
            }
            .introspectTableView { tableView in
                observation = tableView.observe(\.contentOffset) { tableView, change in
                    navigationController?.setNavigationBarHidden(
                        tableView.panGestureRecognizer.translation(in: tableView).y < 0,
                        animated: true
                    )
                }
            }
    }
}

Usage:

struct ContentView: View {
    let list = (0..<100).map { _ in
        UUID().uuidString
    }

    var body: some View {
        NavigationView {
            List(list, id: \.self) { item in
                Text(item)
            }
                .hideNavigationBarOnScroll()
                .navigationBarTitle("Title")
        }
    }
}
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
-2

I researched your request here a bit and found this interesting piece by Mos6y on Medium. Swift/iOS: Determine Scroll Direction

var lastVelocityYSign = 0
func scrollViewDidScroll(_ scrollView: UIScrollView) {
    let currentVelocityY =  scrollView.panGestureRecognizer.velocity(in: scrollView.superview).y
    let currentVelocityYSign = Int(currentVelocityY).signum()
    if currentVelocityYSign != lastVelocityYSign &&
        currentVelocityYSign != 0 {
        lastVelocityYSign = currentVelocityYSign
    }
    if lastVelocityYSign < 0 {
        print("SCROLLING DOWN")
    } else if lastVelocityYSign > 0 {
        print("SCOLLING UP")
    }
}
SaaSy Monster
  • 552
  • 2
  • 16