I want to add an image viewer in my application but I have trouble implementing it. I want to display an image and allow the user to pinch and scroll in the image to examine it in details. Based on what I've gathered from multiple Internet posts, I have something kinda working, but not exactly. Here is my code:
import SwiftUI
struct ContentView: View {
@State var currentScale: CGFloat = 1.0
@State var previousScale: CGFloat = 1.0
@State var currentOffset = CGSize.zero
@State var previousOffset = CGSize.zero
var body: some View {
ZStack {
Image("bulldog2")
.resizable()
.edgesIgnoringSafeArea(.all)
.aspectRatio(contentMode: .fit)
.offset(x: self.currentOffset.width, y: self.currentOffset.height)
.scaleEffect(self.currentScale)
.gesture(DragGesture()
.onChanged { value in
let deltaX = value.translation.width - self.previousOffset.width
let deltaY = value.translation.height - self.previousOffset.height
self.previousOffset.width = value.translation.width
self.previousOffset.height = value.translation.height
self.currentOffset.width = self.currentOffset.width + deltaX / self.currentScale
self.currentOffset.height = self.currentOffset.height + deltaY / self.currentScale }
.onEnded { value in self.previousOffset = CGSize.zero })
.gesture(MagnificationGesture()
.onChanged { value in
let delta = value / self.previousScale
self.previousScale = value
self.currentScale = self.currentScale * delta
}
.onEnded { value in self.previousScale = 1.0 })
VStack {
Spacer()
HStack {
Text("Menu 1").padding().background(Color.white).cornerRadius(30).padding()
Spacer()
Text("Menu 2").padding().background(Color.white).cornerRadius(30).padding()
}
}
}
}
}
The initial view looks like this:
The first problem I have is that I can move the image too far in the way that I can see outside of the image. This can cause the image not to be not visible in the application anymore if it moved too far.
The second problem, which is not a big one, is that I can scale down the image but it becomes too small compared to the view. I want to constraint it so that "fit" would be its smallest size. Is there a better way than constraining self.currentScale
and self.previousScale
?
The third problem is that if I change the image to fill the space, the bottom menu gets larger that the phone's screen.
I'm not an iOS developer and there is probably a much better way to implement this feature. Thanks for you help.
I can answer 2 of 3 questions. I can't repeat the third one.
GeometryReader
and use it's frame
and size
to make some constraints (I'll show it in example below);max
function, like .scaleEffect(max(self.currentScale, 1.0))
.Here is changed example:
struct BulldogImageViewerView: View {
@State var currentScale: CGFloat = 1.0
@State var previousScale: CGFloat = 1.0
@State var currentOffset = CGSize.zero
@State var previousOffset = CGSize.zero
var body: some View {
ZStack {
GeometryReader { geometry in // here you'll have size and frame
Image("bulldog")
.resizable()
.edgesIgnoringSafeArea(.all)
.aspectRatio(contentMode: .fit)
.offset(x: self.currentOffset.width, y: self.currentOffset.height)
.scaleEffect(max(self.currentScale, 1.0)) // the second question
.gesture(DragGesture()
.onChanged { value in
let deltaX = value.translation.width - self.previousOffset.width
let deltaY = value.translation.height - self.previousOffset.height
self.previousOffset.width = value.translation.width
self.previousOffset.height = value.translation.height
let newOffsetWidth = self.currentOffset.width + deltaX / self.currentScale
// question 1: how to add horizontal constraint (but you need to think about scale)
if newOffsetWidth <= geometry.size.width - 150.0 && newOffsetWidth > -150.0 {
self.currentOffset.width = self.currentOffset.width + deltaX / self.currentScale
}
self.currentOffset.height = self.currentOffset.height + deltaY / self.currentScale }
.onEnded { value in self.previousOffset = CGSize.zero })
.gesture(MagnificationGesture()
.onChanged { value in
let delta = value / self.previousScale
self.previousScale = value
self.currentScale = self.currentScale * delta
}
.onEnded { value in self.previousScale = 1.0 })
}
VStack {
Spacer()
HStack {
Text("Menu 1").padding().cornerRadius(30).background(Color.blue).padding()
Spacer()
Text("Menu 2").padding().cornerRadius(30).background(Color.blue).padding()
}
}
}
}
}
with this code I achieved:
user can't move picture away from the screen in horizontal direction;
user can't make picture smaller;