10

I saw the position property but I think that is just used to set x and y, but I don't know what about recognizing current location.

Or totally how to use events income properties like onHover?

rmaddy
  • 314,917
  • 42
  • 532
  • 579
Sajad Beheshti
  • 679
  • 3
  • 8
  • 20

5 Answers5

3

See What is Geometry Reader in SwiftUI?, specifically the discussion about GeometryGetter. If you place a GeometryGetter at the top of your ScrollView contents, it will emit its frame using the binding you pass to it. The origin of this frame will be the negative content offset of the scroll view.

leftspin
  • 2,468
  • 1
  • 25
  • 40
3

In the following example you see how you can use GeometryReader to get the horizontal position of the content in the scroll view. However, I did not succeed yet in finding out how to set the scroll position. (Xcode 11.0 beta 6 (11M392q))

struct TimelineView: View {


    @State private var posX: CGFloat = 0


    var body: some View {

        GeometryReader { geo in

            VStack {

                Text("\(self.posX)")

                ScrollView(.horizontal, showsIndicators: true) {

                    VStack {

                        GeometryReader { innerGeo -> Text in

                            self.posX = innerGeo.frame(in: .global).minX

                            return Text("")
                        }

                        TimelineGridView()
                    }
                }
                .position(x: geo.size.width / 2, y: geo.size.height / 2)
            }
        }
    }
}

where:

struct TimelineGridView: View {


    var body: some View {

        VStack {

            ForEach(0...10, id: \.self) { rowIndex in

                TimelineRowView()
            }
        }
    }
}

struct TimelineRowView: View {


    var body: some View {

        HStack {

            ForEach(0...100, id: \.self) { itemIndex in

                TimelineCellView()
            }
        }
    }
}

struct TimelineCellView: View {


    var body: some View {

        Rectangle()
            .fill(Color.yellow)
            .opacity(0.5)
            .frame(width: 10, height: 10, alignment: .bottomLeading)
    }
}
```
Hardy
  • 4,344
  • 3
  • 17
  • 27
3

You can use TrackableScrollView by @maxnatchanon

Here is the code:

//
//  TrackableScrollView.swift
//  TrackableScrollView
//
//  Created by Frad LEE on 2020/6/21.
//  Copyright © 2020 Frad LEE. All rights reserved.
//

import SwiftUI

/// A trackable and scrollable view. Read [this link](https://medium.com/@maxnatchanon/swiftui-how-to-get-content-offset-from-scrollview-5ce1f84603ec) for more.
///
/// The trackable scroll view displays its content within the trackable scrollable content region.
///
/// # Usage
///
/// ``` swift
/// struct ContentView: View {
///     @State private var scrollViewContentOffset = CGFloat(0) // Content offset available to use
/// 
///     var body: some View {
///         TrackableScrollView(.vertical, showIndicators: false, contentOffset: $scrollViewContentOffset) {
///             ...
///         }
///     }
/// }
/// ```

struct TrackableScrollView<Content>: View where Content: View {
    let axes: Axis.Set
    let showIndicators: Bool
    @Binding var contentOffset: CGFloat
    let content: Content

    /// Creates a new instance that’s scrollable in the direction of the given axis and can show indicators while scrolling.
    /// - Parameters:
    ///   - axes: The scrollable axes of the scroll view.
    ///   - showIndicators: A value that indicates whether the scroll view displays the scrollable component of the content offset, in a way that’s suitable for the platform.
    ///   - contentOffset: A value that indicates  offset of content.
    ///   - content: The scroll view’s content.
    init(_ axes: Axis.Set = .vertical, showIndicators: Bool = true, contentOffset: Binding<CGFloat>, @ViewBuilder content: () -> Content) {
        self.axes = axes
        self.showIndicators = showIndicators
        _contentOffset = contentOffset
        self.content = content()
    }

    var body: some View {
        GeometryReader { outsideProxy in
            ScrollView(self.axes, showsIndicators: self.showIndicators) {
                ZStack(alignment: self.axes == .vertical ? .top : .leading) {
                    GeometryReader { insideProxy in
                        Color.clear
                            .preference(key: ScrollOffsetPreferenceKey.self, value: [self.calculateContentOffset(fromOutsideProxy: outsideProxy, insideProxy: insideProxy)])
                    }
                    VStack {
                        self.content
                    }
                }
            }
            .onPreferenceChange(ScrollOffsetPreferenceKey.self) { value in
                self.contentOffset = value[0]
            }
        }
    }

    private func calculateContentOffset(fromOutsideProxy outsideProxy: GeometryProxy, insideProxy: GeometryProxy) -> CGFloat {
        if axes == .vertical {
            return outsideProxy.frame(in: .global).minY - insideProxy.frame(in: .global).minY
        } else {
            return outsideProxy.frame(in: .global).minX - insideProxy.frame(in: .global).minX
        }
    }
}

struct ScrollOffsetPreferenceKey: PreferenceKey {
    typealias Value = [CGFloat]

    static var defaultValue: [CGFloat] = [0]

    static func reduce(value: inout [CGFloat], nextValue: () -> [CGFloat]) {
        value.append(contentsOf: nextValue())
    }
}
FradSer
  • 199
  • 1
  • 9
  • 8
    I think you should make it much clearer that this is NOT your code, but copied from a GitHub repo - I know you've put "by @maxnatchanon" at the top but it's not clear at all. I was under the impression that you had written it, and was about to credit you with it in a project. – SomaMan Jan 29 '21 at 22:22
  • 2
    Also, be careful with this implementation. The offset changes are returned with a @binding property, which cause the reloading of the view each time the value changes. I had 95% of CPU load while scrolling it. The Majid's method, with a simple completion bloc, is way faster (https://swiftwithmajid.com/2020/09/24/mastering-scrollview-in-swiftui/) – Martin Oct 21 '22 at 12:40
0

If you don't find any option. You can still use the standard UIScrollView with their delegates with UIViewRepresentable by making a separate struct an conforming to it.

More Detail on that you can find on the SwiftUI Tutorials: https://developer.apple.com/tutorials/swiftui/interfacing-with-uikit

SwiftiSwift
  • 7,528
  • 9
  • 56
  • 96
  • Thank you. I don't know why i can't use this example with struct where i can't use "extension". Could you please write me an example code for my case? please help me – Sajad Beheshti Jun 08 '19 at 09:44
0

I created SwiftPackage for this purpose(Pure SwiftUI )

With this lib, you can get position of ScrollView.

https://github.com/kazuooooo/PositionScrollView

Medium post

import Foundation
import SwiftUI

/// Extended ScrollView which can controll position
public struct MinimalHorizontalExample: View, PositionScrollViewDelegate {
    /// Page size of Scroll
    var pageSize = CGSize(width: 200, height: 300)
    
    // Create PositionScrollViewModel
    // (Need to create in parent view to bind the state between this view and PositionScrollView)
    @ObservedObject var psViewModel = PositionScrollViewModel(
        pageSize: CGSize(width: 200, height: 300),
        horizontalScroll: Scroll(
            scrollSetting: ScrollSetting(pageCount: 5, afterMoveType: .stickNearestUnitEdge),
            pageLength: 200 // Page length of direction
        )
    )
   
    public var body: some View {
        return VStack {
            PositionScrollView(
                viewModel: self.psViewModel,
                delegate: self
            ) {
                HStack(spacing: 0) {
                    ForEach(0...4, id: \.self){ i in
                        ZStack {
                            Rectangle()
                                .fill(BLUES[i])
                                .border(Color.black)
                                .frame(
                                    width: self.pageSize.width, height: self.pageSize.height
                            )
                            Text("Page\(i)")
                                .foregroundColor(Color.white)
                                .font(.system(size: 24, weight: .heavy, design: .default))
                        }
                    }
                    
                }
            }
              // Get page via scroll object
            Text("page: \(self.psViewModel.horizontalScroll?.page ?? 0)")
              // Get position via scroll object
            Text("position: \(self.psViewModel.horizontalScroll?.position ?? 0)")
        }
    }
    
    struct SampleView_Previews: PreviewProvider {
        static var previews: some View {
            return MinimalHorizontalExample()
        }
    }
    
    // Delegate methods of PositionScrollView
      // You can monitor changes of position
    public func onScrollStart() {
        print("onScrollStart")
    }
    public func onChangePage(page: Int) {
        print("onChangePage to page: \(page)")
    }
    
    public func onChangePosition(position: CGFloat) {
        print("position: \(position)")
    }
    
    public func onScrollEnd() {
        print("onScrollEnd")
    }
}
kazuwombat
  • 1,515
  • 1
  • 16
  • 19