ios – Swiping Through Multiple Images in FullScreen similar to Facebook


Right now, I am trying to integrate Facebook’s functionality regarding viewing multiple images when in full screen by swiping either left or right to the next or previous image.

// MARK: Media View
extension PostView {
    func mediaView(media: [MediaOBJ]) -> some View {
        VStack {
            if media.count > 1 {
                manyMediaView(media: media)
                    .frame(height: 200)
            } else {
                singleMediaView(media: media[0])
                    .frame(minWidth: 250)
                    .cornerRadius(10)
            }
        }
    }
    
    func manyMediaView(media: [MediaOBJ]) -> some View {
        GeometryReader { geo in
            TabView(selection: $selectedImageIndex) {
                ForEach(media.indices, id: \.self) { index in
                    imageView(media: media[index])
                        .tag(index)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .automatic))
            .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
        }
        .gesture(
            DragGesture().onChanged { value in
                if value.translation.width > 50 {
                    selectedImageIndex = max(selectedImageIndex - 1, 0)
                } else if value.translation.width < -50 {
                    selectedImageIndex = min(selectedImageIndex + 1, media.count - 1)
                }
            }
        )
    }
    
    @ViewBuilder
    func singleMediaView(media: MediaOBJ) -> some View {
        VStack {
            switch media.kind {
            case .photo:
                if media.isGIF {
                    gifView(url: media.link)
                        .frame(height: 200)
                } else {
                    singleImageView(media: media)
                        .cornerRadius(10)
                }
            case .video:
                VideoPlayerView(url: media.link)
                    .frame(height: 300)
            default:
                EmptyView()
            }
        }
    }
    
    func gifView(url: URL) -> some View {
        GIFPlayerView(gifURL: url)
            .maxWidth()
            .frame(height: 200)
            .cornerRadius(10)
    }
    
    
    
    @ViewBuilder
    func singleImageView(media: MediaOBJ) -> some View {
        AsyncImage(url: media.link) { phase in
            switch phase {
            case .success(let image):
                image
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                    .maxHeight(600)
                    .contentShape(Rectangle())
                    .cornerRadius(10)
                    .onTapGesture {
                        self.selectedMedia = .init(image: image)
                    }
            case .failure:
                DefaultPlaceholder()
                    .maxHeight(600)
                    .onAppear {
                        imageLoadKey = .init()
                    }
            case .empty:
                Color.clear
            }
        }
        .id(imageLoadKey)
        .frame(minHeight: 100)
        .background(Color.clear)
        .cornerRadius(10)
        .sheet(item: $selectedMedia) { image in
            SwiftUIImageViewer(image: image.image)
                .onDisappear {
                    self.selectedMedia = nil
                    self.viewModel.reload()
                }
        }
    }
    
    func loadedSingleImage(image: UIImage) -> some View {
        Image(uiImage: image)
            .resizable()
            .aspectRatio(contentMode: .fit)
            .contentShape(Rectangle())
            .cornerRadius(10)
    }
    
    func imageView(media: MediaOBJ) -> some View {
        GeometryReader { geo in
            AsyncImage(url: media.link, content: { image in
                image
                    .resizable()
                    .scaledToFill()
                    .frame(
                        width: geo.size.width,
                        height: 200,
                        alignment: .center
                    )
                    .contentShape(Rectangle())
                    .onTapGesture  {
                        self.selectedMedia = .init(image: image)
                    }
            }, placeholder: {
                DefaultPlaceholder()
            })
            .frame(
                width: geo.size.width,
                height: 200,
                alignment: .center
            )
            .background(Color.lightestGray)
            .cornerRadius(10)
            .sheet(item: $selectedMedia) { image in
                //                SwiftUIImageViewer(image: image.image)
                MultiImageViewer(images: [image.image])
                    .onDisappear {
                        self.selectedMedia = nil
                        self.viewModel.reload()
                    }
            }
        }
    }
    
    func videoView(avPlayer: AVPlayer) -> some View {
        VideoPlayer(player: avPlayer)
            .scaledToFill()
            .maxWidth()
            .frame(height: 300, alignment: .center)
            .cornerRadius(10)
            .onAppear {
                avPlayer.play()
            }
            .onTapGesture {
                if avPlayer.isPlaying {
                    avPlayer.pause()
                } else {
                    avPlayer.play()
                }
            }
    }
    
    func audioView(media: MediaOBJ) -> some View {
        UText("")
    }
}

As you can see, as is I can swipe left or right to view the next or previous image in manyMediaView in PostView. However, when I try to view the images in full screen via MultiImageViewer , which is called in imageView(), I can only do so one at a time instead of swiping through them.

Here is MultiImageViewer:

import UIKit

public struct MultiImageViewer: View {
    @Environment(\.presentationMode) var presentationMode
    
    let images: [Image]
    
    @State private var scale: CGFloat = 1
    @State private var lastScale: CGFloat = 1
    
    @State private var offset: CGPoint = .zero
    @State private var lastTranslation: CGSize = .zero
    
    @State private var selectedImageIndex = 0
    
    public init(images: [Image]) {
        self.images = images
    }
    
    public var body: some View {
        NavigationStack {
            content
                .toolbar {
                    ToolbarItem(placement: .topBarTrailing) {
                        Button {
                            presentationMode.wrappedValue.dismiss()
                        } label: {
                            Image("close-circle")
                                .resizable()
                                .scaledToFit()
                                .frame(width: 20, height: 20, alignment: .center)
                        }
                    }
                }
        }
    }
    
    var content: some View {
        GeometryReader { proxy in
            TabView(selection: $selectedImageIndex) {
                ForEach(images.indices, id: \.self) { index in
                    images[index]
                        .resizable()
                        .scaledToFit()
                        .gesture(
                            DragGesture().onChanged { value in
                                let swipeThreshold: CGFloat = 50
                                
                                if value.translation.width > swipeThreshold && selectedImageIndex > 0 {
                                    selectedImageIndex -= 1
                                } else if value.translation.width < -swipeThreshold {
                                    selectedImageIndex = min(selectedImageIndex + 1, images.count - 1)
                                }
                            }
                        )
                        .tag(index)
                }
            }
            .tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
            .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
            .edgesIgnoringSafeArea(.all)
        }
    }
    
    private func makeMagnificationGesture(size: CGSize) -> some Gesture {
        MagnificationGesture()
            .onChanged { value in
                let delta = value / lastScale
                lastScale = value
                
                // To minimize jittering
                if abs(1 - delta) > 0.01 {
                    scale *= delta
                }
            }
            .onEnded { _ in
                lastScale = 1
                if scale < 1 {
                    withAnimation {
                        scale = 1
                    }
                }
                adjustMaxOffset(size: size)
            }
    }
    
    private func makeDragGesture(size: CGSize) -> some Gesture {
        DragGesture()
            .onChanged { value in
                let diff = CGPoint(
                    x: value.translation.width - lastTranslation.width,
                    y: value.translation.height - lastTranslation.height
                )
                offset = .init(x: offset.x + diff.x, y: offset.y + diff.y)
                lastTranslation = value.translation
            }
            .onEnded { _ in
                adjustMaxOffset(size: size)
            }
    }
    
    private func adjustMaxOffset(size: CGSize) {
        let maxOffsetX = (size.width * (scale - 1)) / 2
        let maxOffsetY = (size.height * (scale - 1)) / 2
        
        var newOffsetX = offset.x
        var newOffsetY = offset.y
        
        if abs(newOffsetX) > maxOffsetX {
            newOffsetX = maxOffsetX * (abs(newOffsetX) / newOffsetX)
        }
        if abs(newOffsetY) > maxOffsetY {
            newOffsetY = maxOffsetY * (abs(newOffsetY) / newOffsetY)
        }
        
        let newOffset = CGPoint(x: newOffsetX, y: newOffsetY)
        if newOffset != offset {
            withAnimation {
                offset = newOffset
            }
        }
        self.lastTranslation = .zero
    }
}

What is needed so that, like Facebook, I can swipe through each image in full screen when it comes to a post with multiple images?

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img