diff --git a/MiniScanner.xcodeproj/project.pbxproj b/MiniScanner.xcodeproj/project.pbxproj index 7d7cb69bf3fe9e754fb30d6df3076ad976524bbf..4b814676b65df7187e385796c218ed5a8a7486b4 100644 --- a/MiniScanner.xcodeproj/project.pbxproj +++ b/MiniScanner.xcodeproj/project.pbxproj @@ -162,6 +162,8 @@ 6709C05C2C6DFE3C009C3F11 /* UIPrimaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6709C05B2C6DFE3C009C3F11 /* UIPrimaryButton.swift */; }; 6709C05E2C6DFE44009C3F11 /* UISecondaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6709C05D2C6DFE44009C3F11 /* UISecondaryButton.swift */; }; 6709C0602C6E0005009C3F11 /* UITertiaryButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6709C05F2C6E0005009C3F11 /* UITertiaryButton.swift */; }; + 6709C0622C6E2373009C3F11 /* ZoomableScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6709C0612C6E2373009C3F11 /* ZoomableScrollView.swift */; }; + 6709C0642C6E2495009C3F11 /* ZoomableImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6709C0632C6E2495009C3F11 /* ZoomableImageView.swift */; }; 67106C942C4EAC0100874BFC /* CustomTabBarLayerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67106C922C4EAC0000874BFC /* CustomTabBarLayerProtocol.swift */; }; 67106C952C4EAC0100874BFC /* CustomTabBarLayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 67106C932C4EAC0000874BFC /* CustomTabBarLayer.swift */; }; 672C46442C47B1F300497EF0 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 672C46432C47B1F300497EF0 /* Localizable.xcstrings */; }; @@ -494,6 +496,8 @@ 6709C05B2C6DFE3C009C3F11 /* UIPrimaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIPrimaryButton.swift; sourceTree = "<group>"; }; 6709C05D2C6DFE44009C3F11 /* UISecondaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISecondaryButton.swift; sourceTree = "<group>"; }; 6709C05F2C6E0005009C3F11 /* UITertiaryButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITertiaryButton.swift; sourceTree = "<group>"; }; + 6709C0612C6E2373009C3F11 /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableScrollView.swift; sourceTree = "<group>"; }; + 6709C0632C6E2495009C3F11 /* ZoomableImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ZoomableImageView.swift; sourceTree = "<group>"; }; 67106C922C4EAC0000874BFC /* CustomTabBarLayerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomTabBarLayerProtocol.swift; sourceTree = "<group>"; }; 67106C932C4EAC0000874BFC /* CustomTabBarLayer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomTabBarLayer.swift; sourceTree = "<group>"; }; 672C46432C47B1F300497EF0 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; }; @@ -1628,6 +1632,8 @@ 67A2AF742C6B6F5100039F30 /* SettingButton.swift */, 67A2AF7A2C6B759100039F30 /* SettingSheetView.swift */, 67E6A1972C65151F00A77F29 /* ImageCompressView.swift */, + 6709C0632C6E2495009C3F11 /* ZoomableImageView.swift */, + 6709C0612C6E2373009C3F11 /* ZoomableScrollView.swift */, ); path = Presentation; sourceTree = "<group>"; @@ -2024,6 +2030,7 @@ 672C46DC2C48018D00497EF0 /* UIColors+Colors.swift in Sources */, 67A2AF7B2C6B759100039F30 /* SettingSheetView.swift in Sources */, 672C46662C47C74C00497EF0 /* Inject.swift in Sources */, + 6709C0622C6E2373009C3F11 /* ZoomableScrollView.swift in Sources */, 535983F22C144E87003EB6ED /* Keys.swift in Sources */, 539996962C27130000671340 /* ConstraintOffsetTarget.swift in Sources */, EC0CF21D254D8F3900888722 /* String+Extensions.swift in Sources */, @@ -2064,6 +2071,7 @@ 53BEB1472C2968F8005A3567 /* FolderSelectCollectionViewCell.swift in Sources */, 539996972C27130000671340 /* ConstraintMakerEditable.swift in Sources */, 539996A02C27130000671340 /* LayoutConstraint.swift in Sources */, + 6709C0642C6E2495009C3F11 /* ZoomableImageView.swift in Sources */, 53014F972C11A8E80071CE39 /* AVCaptureVideoOrientation+Utils.swift in Sources */, 67A2AF872C6B8B2A00039F30 /* PrimaryButton.swift in Sources */, 535983EF2C142C9F003EB6ED /* Localization.swift in Sources */, diff --git a/MiniScanner/Features/Settings/Presentation/ImageCompressView.swift b/MiniScanner/Features/Settings/Presentation/ImageCompressView.swift index 9f220b2dc2695863271ef858a0f1410851b6747e..e1c58c27c7e8a5580114a82bed79c28a22bda323 100644 --- a/MiniScanner/Features/Settings/Presentation/ImageCompressView.swift +++ b/MiniScanner/Features/Settings/Presentation/ImageCompressView.swift @@ -14,81 +14,85 @@ struct ImageCompressView: View { @Binding var isPresented: Bool var image: UIImage - var columns = [GridItem(.flexible()), GridItem(.flexible())] + var columns = [GridItem(.flexible()), GridItem(.flexible()), GridItem(.flexible())] @State private var images: [[UIImage]] = Array(repeating: Array(repeating: UIImage(), count: 4), count: 4) + @State var selectedImage: UIImage? + @State var selectedImageTitle: String = "" + @State var isZoomableImagePresented: Bool = false + var body: some View { - ScrollView(showsIndicators: false) { + VStack(alignment: .leading, spacing: 8) { - VStack(alignment: .leading, spacing: 16) { - - close - - imagesGrid("") { - - original - - uiImage1 - - timeline1 - - mozjpeg1 - } - - imagesGrid("") { - - original - - uiImage2 - - timeline2 - - mozjpeg2 - } - - imagesGrid("") { - - original - - uiImage3 - - timeline3 - - mozjpeg3 - } + close + .padding(.leading, 8) + + ScrollView(showsIndicators: false) { - imagesGrid("Dec") { + VStack(spacing: 16) { original - decMozjpeg1 + imagesGrid("") { + + uiImage1 + + timeline1 + + mozjpeg1 + + uiImage2 + + timeline2 + + mozjpeg2 + + uiImage3 + + timeline3 + + mozjpeg3 + } - decMozjpeg2 - - decMozjpeg3 + imagesGrid("Dec") { + + decMozjpeg1 + + decMozjpeg2 + + decMozjpeg3 + } } + .padding(.all, 8) } - .padding(.all, 8) } .task { await prepareImages() } + .fullScreenCover(isPresented: $isZoomableImagePresented) { + + if let selectedImage { + ZoomableImageView(isPresented: $isZoomableImagePresented, + title: selectedImageTitle, + size: size(image: selectedImage), + image: selectedImage) + } + } } private var close: some View { Button(action: { isPresented = false }, label: { Image.xMark + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 16, height: 16) .customForeground(.gray600) }) } private func prepareImages() async { - images[0][0] = image - images[1][0] = image - images[2][0] = image - images[0][1] = compressImages(image: image, compressionQuality: 1) images[1][1] = compressImages(image: image, compressionQuality: 0.5) images[2][1] = compressImages(image: image, compressionQuality: 0) @@ -179,7 +183,7 @@ extension ImageCompressView { Text(title) - LazyVGrid(columns: columns, alignment: .center) { + LazyVGrid(columns: columns) { content() } .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -197,6 +201,11 @@ extension ImageCompressView { .resizable() .aspectRatio(contentMode: .fit) } + .onTapGesture { + selectedImageTitle = title + selectedImage = image + isZoomableImagePresented = true + } } private func size(image: UIImage) -> String { diff --git a/MiniScanner/Features/Settings/Presentation/ZoomableImageView.swift b/MiniScanner/Features/Settings/Presentation/ZoomableImageView.swift new file mode 100644 index 0000000000000000000000000000000000000000..71a62c35c0fb7a8d13024b884a0bb340ba5823da --- /dev/null +++ b/MiniScanner/Features/Settings/Presentation/ZoomableImageView.swift @@ -0,0 +1,53 @@ +// +// ZoomableImageView.swift +// MiniScanner +// +// Created by Mustafa Merza on 8/15/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +struct ZoomableImageView: View { + + @Binding var isPresented: Bool + + var title: String + var size: String + var image: UIImage + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + + close + .padding(.leading, 8) + + Text(title) + .frame(maxWidth: .infinity) + + Text(size) + .frame(maxWidth: .infinity) + + ZoomableScrollView { + + Image(uiImage: image) + .resizable() + .aspectRatio(contentMode: .fit) + } + } + } + + private var close: some View { + Button(action: { isPresented = false }, label: { + Image.xMark + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 16, height: 16) + .customForeground(.gray600) + }) + } +} + +#Preview { + ZoomableImageView(isPresented: .constant(true), title: "Title", size: "Size", image: .actions) +} diff --git a/MiniScanner/Features/Settings/Presentation/ZoomableScrollView.swift b/MiniScanner/Features/Settings/Presentation/ZoomableScrollView.swift new file mode 100644 index 0000000000000000000000000000000000000000..9913b55dce52bd1b1ef132c46542b9031cdb2137 --- /dev/null +++ b/MiniScanner/Features/Settings/Presentation/ZoomableScrollView.swift @@ -0,0 +1,61 @@ +// +// ZoomableScrollView.swift +// MiniScanner +// +// Created by Mustafa Merza on 8/15/24. +// Copyright © 2024 AppsNectar. All rights reserved. +// + +import SwiftUI + +struct ZoomableScrollView<Content: View>: UIViewRepresentable { + + private var content: Content + + init(@ViewBuilder content: () -> Content) { + self.content = content() + } + + func makeUIView(context: Context) -> UIScrollView { + + let scrollView = UIScrollView() + scrollView.delegate = context.coordinator + scrollView.maximumZoomScale = 30 + scrollView.minimumZoomScale = 1 + scrollView.bouncesZoom = true + + let hostedView = context.coordinator.hostingController.view! + hostedView.translatesAutoresizingMaskIntoConstraints = true + hostedView.autoresizingMask = [.flexibleWidth, .flexibleHeight] + hostedView.frame = scrollView.bounds + scrollView.addSubview(hostedView) + + return scrollView + } + + func updateUIView(_ uiView: UIScrollView, context: Context) { + context.coordinator.hostingController.rootView = self.content + assert(context.coordinator.hostingController.view.superview == uiView) + } + + func makeCoordinator() -> Coordinator { + Coordinator(hostingController: UIHostingController(rootView: self.content)) + } + + class Coordinator: NSObject, UIScrollViewDelegate { + + var hostingController: UIHostingController<Content> + + init(hostingController: UIHostingController<Content>) { + self.hostingController = hostingController + } + + func viewForZooming(in scrollView: UIScrollView) -> UIView? { + hostingController.view + } + } +} + +#Preview { + ZoomableScrollView { } +}