I would like to build a simple view that allows me to show an image in a scroll view and let the user pinch to zoom on the image, and pan.
I've looked around and started with thisScrollView
:
struct TestView: View {
var body: some View {
ScrollView {
Image("test")
.border(Color(.yellow))
}
.border(Color(.red))
}
}
That would not handle zooming.
I then did this:
struct TestView: View {
/// https://wp.usatodaysports.com/wp-content/uploads/sites/88/2014/03/sunset-in-the-dolos-mikadun.jpg
var image = UIImage(named: "test")!
@State var scale: CGFloat = 1.0
@State var lastScaleValue: CGFloat = 1.0
var body: some View {
GeometryReader { geo in
ScrollView([.vertical, .horizontal], showsIndicators: false){
ZStack{
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width, height: geo.size.width)
.scaleEffect(scale)
.gesture(MagnificationGesture().onChanged { val in
let delta = val / self.lastScaleValue
self.lastScaleValue = val
var newScale = self.scale * delta
if newScale < 1.0 {
newScale = 1.0
}
scale = newScale
}.onEnded{val in
lastScaleValue = 1
})
}
}
.frame(width: geo.size.width, height: geo.size.width)
.border(Color(.red))
.background(Color(.systemBackground).edgesIgnoringSafeArea(.all))
}
}
}
This allows me to zoom in and out, however, I cannot pan the image:
How can I code up things so I can support zoom and panning?
in order to get the panning functionality you will have to change the size of your Image container, in this case the ZStack. So first we need a variable to save the current latest scale value.
@State var scaledFrame: CGFloat = 1.0
Then just change the size of the container each time the gesture ends.
ZStack{
Image(uiImage: image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: geo.size.width, height: geo.size.width )
.scaleEffect(scale)
.gesture(MagnificationGesture().onChanged { val in
let delta = val / self.lastScaleValue
self.lastScaleValue = val
var newScale = self.scale * delta
if newScale < 1.0 {
newScale = 1.0
}
scale = newScale
}.onEnded{val in
scaledFrame = scale//Update the value once the gesture is over
lastScaleValue = 1
})
.draggable()
}
.frame(width: geo.size.width * scaledFrame, height: geo.size.width * scaledFrame)
//change the size of the frame once the drag is complete
This is due to the way ScrollView works, when you were zooming in, the real size of the container was not changing, therefore the ScrollView was only moving accordingly.