5

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.

4
  • add some additional info, see this may be it helps you : How to make a SwiftUI List scroll automatically? Commented Aug 31, 2021 at 14:14
  • Is there any reason that you use UIKit? How about using custom ScrollView in pure SwiftUI? Commented Aug 31, 2021 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. Commented Sep 2, 2021 at 19:16
  • Does this answer your question stackoverflow.com/a/63216812/12299030? Commented Sep 7, 2021 at 12:53

3 Answers 3

5

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.

Sign up to request clarification or add additional context in comments.

4 Comments

I know this, but how will I determine scroll direction with the offset.
In your case offset will be equivalent to scrollView.panGestureRecognizer.translation(in: scrollView).y
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 can you share the solution you did using geometryReader? May be I can help with lag.
1

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")
        }
    }
}

Comments

-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")
    }
}

2 Comments

This is a UIKit solution
Please read the question before answering

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.