Added zoom gesture to expanded view

This commit is contained in:
breadone 2024-07-09 18:49:07 +12:00
parent ccdf7db12e
commit 565d80910d
No known key found for this signature in database
4 changed files with 187 additions and 1 deletions

View File

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
46410F842C3D12B100FFBF7E /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46410F832C3D12B100FFBF7E /* ZoomableScrollView.swift */; };
466989C72C34DC7A009884D1 /* JustScanItApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466989C62C34DC7A009884D1 /* JustScanItApp.swift */; };
466989CB2C34DC7B009884D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 466989CA2C34DC7B009884D1 /* Assets.xcassets */; };
466989CE2C34DC7B009884D1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 466989CD2C34DC7B009884D1 /* Preview Assets.xcassets */; };
@ -19,6 +20,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
46410F832C3D12B100FFBF7E /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; };
466989C32C34DC7A009884D1 /* JustScanIt.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = JustScanIt.app; sourceTree = BUILT_PRODUCTS_DIR; };
466989C62C34DC7A009884D1 /* JustScanItApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JustScanItApp.swift; sourceTree = "<group>"; };
466989C82C34DC7A009884D1 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
@ -83,6 +85,7 @@
isa = PBXGroup;
children = (
466989D52C34DCE4009884D1 /* VNDocumentCameraViewControllerR.swift */,
46410F832C3D12B100FFBF7E /* ZoomableScrollView.swift */,
);
path = Controllers;
sourceTree = "<group>";
@ -182,6 +185,7 @@
466989D92C35206E009884D1 /* Scan.swift in Sources */,
466989DF2C353FEC009884D1 /* SettingsView.swift in Sources */,
466989D62C34DCE4009884D1 /* VNDocumentCameraViewControllerR.swift in Sources */,
46410F842C3D12B100FFBF7E /* ZoomableScrollView.swift in Sources */,
466989DD2C35299D009884D1 /* ScanItemView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;

View File

@ -0,0 +1,162 @@
//
// ZoomableScrollView.swift
// JustScanIt
//
// Created by Pradyun Setti on 09/07/2024.
//
import Foundation
import UIKit
import SwiftUI
class PinchZoomView: UIView {
weak var delegate: PinchZoomViewDelgate?
private(set) var scale: CGFloat = 0 {
didSet {
delegate?.pinchZoomView(self, didChangeScale: scale)
}
}
private(set) var anchor: UnitPoint = .center {
didSet {
delegate?.pinchZoomView(self, didChangeAnchor: anchor)
}
}
private(set) var offset: CGSize = .zero {
didSet {
delegate?.pinchZoomView(self, didChangeOffset: offset)
}
}
private(set) var isPinching: Bool = false {
didSet {
delegate?.pinchZoomView(self, didChangePinching: isPinching)
}
}
private var startLocation: CGPoint = .zero
private var location: CGPoint = .zero
private var numberOfTouches: Int = 0
init() {
super.init(frame: .zero)
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(pinch(gesture:)))
pinchGesture.cancelsTouchesInView = false
addGestureRecognizer(pinchGesture)
}
required init?(coder: NSCoder) {
fatalError()
}
@objc private func pinch(gesture: UIPinchGestureRecognizer) {
switch gesture.state {
case .began:
isPinching = true
startLocation = gesture.location(in: self)
anchor = UnitPoint(x: startLocation.x / bounds.width, y: startLocation.y / bounds.height)
numberOfTouches = gesture.numberOfTouches
case .changed:
if gesture.numberOfTouches != numberOfTouches {
// If the number of fingers being used changes, the start location needs to be adjusted to avoid jumping.
let newLocation = gesture.location(in: self)
let jumpDifference = CGSize(width: newLocation.x - location.x, height: newLocation.y - location.y)
startLocation = CGPoint(x: startLocation.x + jumpDifference.width, y: startLocation.y + jumpDifference.height)
numberOfTouches = gesture.numberOfTouches
}
scale = gesture.scale
location = gesture.location(in: self)
offset = CGSize(width: location.x - startLocation.x, height: location.y - startLocation.y)
case .ended, .cancelled, .failed:
isPinching = false
scale = 1.0
anchor = .center
offset = .zero
default:
break
}
}
}
protocol PinchZoomViewDelgate: AnyObject {
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool)
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat)
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint)
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize)
}
struct PinchZoom: UIViewRepresentable {
@Binding var scale: CGFloat
@Binding var anchor: UnitPoint
@Binding var offset: CGSize
@Binding var isPinching: Bool
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> PinchZoomView {
let pinchZoomView = PinchZoomView()
pinchZoomView.delegate = context.coordinator
return pinchZoomView
}
func updateUIView(_ pageControl: PinchZoomView, context: Context) { }
class Coordinator: NSObject, PinchZoomViewDelgate {
var pinchZoom: PinchZoom
init(_ pinchZoom: PinchZoom) {
self.pinchZoom = pinchZoom
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangePinching isPinching: Bool) {
pinchZoom.isPinching = isPinching
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeScale scale: CGFloat) {
pinchZoom.scale = scale
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeAnchor anchor: UnitPoint) {
pinchZoom.anchor = anchor
}
func pinchZoomView(_ pinchZoomView: PinchZoomView, didChangeOffset offset: CGSize) {
pinchZoom.offset = offset
}
}
}
struct PinchToZoom: ViewModifier {
@State var scale: CGFloat = 1.0
@State var anchor: UnitPoint = .center
@State var offset: CGSize = .zero
@State var isPinching: Bool = false
func body(content: Content) -> some View {
content
.scaleEffect(scale, anchor: anchor)
.offset(offset)
.animation(.spring, value: isPinching)
.overlay(PinchZoom(scale: $scale, anchor: $anchor, offset: $offset, isPinching: $isPinching))
}
}
extension View {
func pinchToZoom() -> some View {
self.modifier(PinchToZoom())
}
}

View File

@ -41,6 +41,7 @@ struct MainView: View {
var body: some View {
NavigationView {
ScrollView {
// newScanButton
ForEach(scans, id: \.self) { scan in
ScanItemView(scan)
}
@ -72,6 +73,18 @@ struct MainView: View {
}
}
var newScanButton: some View {
Button { showVNDocumentCameraView() } label: {
Text("New Scan")
.frame(maxWidth: .infinity)
.padding(12)
.foregroundStyle(.white)
.background(.blue)
.clipShape(RoundedRectangle(cornerRadius: 15))
}
.padding(.horizontal)
}
private func showVNDocumentCameraView() {
Task { guard await isAuthorized else { return } }
@ -95,5 +108,11 @@ struct MainView: View {
}
#Preview {
MainView()
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try! ModelContainer(for: Scan.self, configurations: config)
let scan = Scan(images: [UIImage(systemName: "questionmark")!])
container.mainContext.insert(scan)
return MainView().modelContainer(container)
}

View File

@ -96,6 +96,7 @@ struct ScanItemView: View {
.resizable()
.scaledToFit()
.frame(maxHeight: 400)
.pinchToZoom()
.onTapGesture {
withAnimation(.easeInOut(duration: 0.3)) {
isZoomed = false