Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to pinch and scroll an image in SwiftUI?

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:

enter image description here

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.

enter image description here

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?

enter image description here

The third problem is that if I change the image to fill the space, the bottom menu gets larger that the phone's screen.

enter image description here

I'm not an iOS developer and there is probably a much better way to implement this feature. Thanks for you help.

like image 450
Vincent Garcia Avatar asked Nov 17 '25 09:11

Vincent Garcia


1 Answers

I can answer 2 of 3 questions. I can't repeat the third one.

  1. you can use GeometryReader and use it's frame and size to make some constraints (I'll show it in example below);
  2. maybe the most simplest and better way is just to use 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;

like image 144
Hrabovskyi Oleksandr Avatar answered Nov 20 '25 02:11

Hrabovskyi Oleksandr