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?