Added zoom gesture to expanded view
This commit is contained in:
parent
ccdf7db12e
commit
565d80910d
@ -7,6 +7,7 @@
|
|||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* 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 */; };
|
466989C72C34DC7A009884D1 /* JustScanItApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 466989C62C34DC7A009884D1 /* JustScanItApp.swift */; };
|
||||||
466989CB2C34DC7B009884D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 466989CA2C34DC7B009884D1 /* Assets.xcassets */; };
|
466989CB2C34DC7B009884D1 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 466989CA2C34DC7B009884D1 /* Assets.xcassets */; };
|
||||||
466989CE2C34DC7B009884D1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 466989CD2C34DC7B009884D1 /* Preview Assets.xcassets */; };
|
466989CE2C34DC7B009884D1 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 466989CD2C34DC7B009884D1 /* Preview Assets.xcassets */; };
|
||||||
@ -19,6 +20,7 @@
|
|||||||
/* End PBXBuildFile section */
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
/* Begin PBXFileReference 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; };
|
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>"; };
|
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>"; };
|
466989C82C34DC7A009884D1 /* MainView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainView.swift; sourceTree = "<group>"; };
|
||||||
@ -83,6 +85,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
466989D52C34DCE4009884D1 /* VNDocumentCameraViewControllerR.swift */,
|
466989D52C34DCE4009884D1 /* VNDocumentCameraViewControllerR.swift */,
|
||||||
|
46410F832C3D12B100FFBF7E /* ZoomableScrollView.swift */,
|
||||||
);
|
);
|
||||||
path = Controllers;
|
path = Controllers;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -182,6 +185,7 @@
|
|||||||
466989D92C35206E009884D1 /* Scan.swift in Sources */,
|
466989D92C35206E009884D1 /* Scan.swift in Sources */,
|
||||||
466989DF2C353FEC009884D1 /* SettingsView.swift in Sources */,
|
466989DF2C353FEC009884D1 /* SettingsView.swift in Sources */,
|
||||||
466989D62C34DCE4009884D1 /* VNDocumentCameraViewControllerR.swift in Sources */,
|
466989D62C34DCE4009884D1 /* VNDocumentCameraViewControllerR.swift in Sources */,
|
||||||
|
46410F842C3D12B100FFBF7E /* ZoomableScrollView.swift in Sources */,
|
||||||
466989DD2C35299D009884D1 /* ScanItemView.swift in Sources */,
|
466989DD2C35299D009884D1 /* ScanItemView.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
162
JustScanIt/Controllers/ZoomableScrollView.swift
Normal file
162
JustScanIt/Controllers/ZoomableScrollView.swift
Normal 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())
|
||||||
|
}
|
||||||
|
}
|
@ -41,6 +41,7 @@ struct MainView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
|
// newScanButton
|
||||||
ForEach(scans, id: \.self) { scan in
|
ForEach(scans, id: \.self) { scan in
|
||||||
ScanItemView(scan)
|
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() {
|
private func showVNDocumentCameraView() {
|
||||||
Task { guard await isAuthorized else { return } }
|
Task { guard await isAuthorized else { return } }
|
||||||
|
|
||||||
@ -95,5 +108,11 @@ struct MainView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#Preview {
|
#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)
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,7 @@ struct ScanItemView: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(maxHeight: 400)
|
.frame(maxHeight: 400)
|
||||||
|
.pinchToZoom()
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
withAnimation(.easeInOut(duration: 0.3)) {
|
withAnimation(.easeInOut(duration: 0.3)) {
|
||||||
isZoomed = false
|
isZoomed = false
|
||||||
|
Loading…
x
Reference in New Issue
Block a user