Skip to content

Listen ScrollView contentOffset and set it #54

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
phuhk opened this issue Sep 9, 2020 · 4 comments
Closed

Listen ScrollView contentOffset and set it #54

phuhk opened this issue Sep 9, 2020 · 4 comments

Comments

@phuhk
Copy link

phuhk commented Sep 9, 2020

hi there, I'm late for the party. As title, can we using this library to do something like this? Listen ScrollView contentOffset and set it. Sorry if I asking question at wrong place.

@lucastimeless
Copy link

In iOS 14 you can use: ScrollViewReader to set the content offset.

In iOS 14 for listening to the contentOffset you could do something like this (although with the current version you will need: PR #56 or another fix for issue #55)

import SwiftUI
import Introspect
import Combine

struct ScrollViewDidScrollViewModifier: ViewModifier {
    
    class ViewModel: ObservableObject {
        var contentOffsetSubscription: AnyCancellable?
    }

    @StateObject var viewModel = ViewModel()
    var didScroll: (CGPoint) -> Void
    
    func body(content: Content) -> some View {
        content
        .introspectScrollView { scrollView in
            if viewModel.contentOffsetSubscription == nil {
                viewModel.contentOffsetSubscription = scrollView.publisher(for: \.contentOffset)
                .sink { contentOffset in
                    didScroll(contentOffset)
                }
            }
        }
    }
}

extension ScrollView {
    func didScroll(_ didScroll: @escaping (CGPoint) -> Void) -> some View {
        self.modifier(ScrollViewDidScrollViewModifier(didScroll: didScroll))
    }
}

struct ContentView: View {
    var body: some View {
        ScrollView {
            ForEach(1..<21) { _ in 
                Text("Hello, world!")
                    .padding()
            }
        }
        .didScroll { contentOffset in
            print(contentOffset)
        }
    }
}

@JannThomas
Copy link
Collaborator

JannThomas commented Mar 19, 2021

This is not an Introspect Issue, but @lucastimeless has also provided a nice solution. That said I will close this issue.

@chrysb
Copy link

chrysb commented Jul 16, 2021

if viewModel.contentOffsetSubscription == nil {
                viewModel.contentOffsetSubscription = scrollView.publisher(for: \.contentOffset)
                .sink { contentOffset in
                    didScroll(contentOffset)
                }
            }

Please be careful with this solution, as sinking the publisher within the view creates a retain and your view (and related viewmodels) will not be de-allocated.

Here's the fix:

import Foundation
import SwiftUI
import Combine

struct ScrollViewDidScrollViewModifier: ViewModifier {
  class ViewModel: ObservableObject {
    @Published var contentOffset: CGPoint = .zero
    
    var contentOffsetSubscription: AnyCancellable?
    
    func subscribe(scrollView: UIScrollView) {
      contentOffsetSubscription = scrollView.publisher(for: \.contentOffset).sink { [weak self] contentOffset in
        self?.contentOffset = contentOffset
      }
    }
  }

  @StateObject var viewModel = ViewModel()
  var didScroll: (CGPoint) -> Void
  
  func body(content: Content) -> some View {
    content
      .introspectScrollView { scrollView in
        if viewModel.contentOffsetSubscription == nil {
          viewModel.subscribe(scrollView: scrollView)
        }
      }
      .onReceive(viewModel.$contentOffset) { contentOffset in
        didScroll(contentOffset)
      }
  }
}

extension View {
  func didScroll(_ didScroll: @escaping (CGPoint) -> Void) -> some View {
    self.modifier(ScrollViewDidScrollViewModifier(didScroll: didScroll))
  }
}

@hyouuu
Copy link

hyouuu commented Sep 1, 2022

@lucastimeless thanks for the answer! However it keeps publishing values and the CPU usage is at 100% - is there a way to only listen to value changes?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants