简体   繁体   中英

SwiftUI: Infinite Scrolling Horizontal ScrollView

I need to create an infinite horizontal scrolling with ScrollView to scroll a panorama image. So I would like to make this scrolling infinite from both sides, is there any possible way to make it? I have searched maybe using ScrollViewReader can achieve this but no luck.

ScrollView(.horizontal, showsIndicators: false) {
            Image("panorama")
                .resizable()
                .scaledToFill()
        } 

Actually for such prepared image it is enough to load it only once, and we need only to Image presenters for it (note: they do not copy memory, but use the same loaded image). Everything else is just about layout on the fly moving current of-screen item depending on drag direction to left or to the right of current one.

Tested with Xcode 13.4 / iOS 15.5

演示

Main part of code:

struct PanoramaView: View {
    let image: UIImage

    private struct Item: Identifiable, Equatable {
        let id = UUID()
        var pos: CGFloat!
    }

    @State private var items = [Item(), Item()]

    // ...

    var body: some View {
        GeometryReader { gp in
            let centerY = gp.size.height / 2
            ForEach($items) { $item in
                Image(uiImage: image)
                    .resizable().aspectRatio(contentMode: .fill)
                    .position(x: item.pos ?? 0, y: centerY)
                    .offset(x: dragOffset)
            }
        }
        .background(GeometryReader {
            Color.clear.preference(key: ViewSizeKey.self,
                                   value: $0.frame(in: .local).size)
        })
        .onPreferenceChange(ViewSizeKey.self) {
            setupLayout($0)
        }
        .contentShape(Rectangle())
        .gesture(scrollGesture)
    }

and usage

    let panorama = UIImage(contentsOfFile: Bundle.main.path(forResource: "panorama", ofType: "jpeg")!)!
    var body: some View {
        PanoramaView(image: panorama)
            .frame(height: 300)   // to demo of dynamic internal layout
    }

Complete test code is here

You can put 3 identical panorama images next to each other (to be able to scroll over the edges) and add a custom DragGesture, that basically jumps back to the relative position of the middle image.

This image is a bad example, obviously it will work better with a real 360° image;)

EDIT :
now with predicted end location + animation and 5 images.

在此处输入图像描述

struct ContentView: View {
    
    @State private var dragOffset = CGFloat.zero
    @State private var offset = CGFloat.zero

    let imageWidth: CGFloat = 500
    
    var body: some View {
        HStack(spacing: 0) {
            ForEach(0..<5) { _ in
                Image("image")
                    .resizable()
                    .frame(width: imageWidth)
            }
        }
        .offset(x: offset + dragOffset)
        .gesture(
            DragGesture()
                .onChanged({ value in
                    dragOffset = value.translation.width
                })
                .onEnded({ value in
                    withAnimation(.easeOut) {
                        dragOffset = value.predictedEndTranslation.width
                    }
                    offset = (offset + dragOffset).remainder(dividingBy: imageWidth)
                    dragOffset = 0
                })
        )
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM