From 6d541f9798ccfd30cadeb962b28d83b8a754a5b2 Mon Sep 17 00:00:00 2001 From: Vegard Matthey Date: Mon, 7 Jul 2025 19:56:42 +0200 Subject: [PATCH] add bar, gestures for switching between images --- .gitignore | 3 + ImageViewer.xcodeproj/project.pbxproj | 66 ++----- ImageViewer/Info.plist | 7 +- ImageViewer/ViewController.swift | 245 +++++++++++++++++++++++++- run.sh | 4 + 5 files changed, 269 insertions(+), 56 deletions(-) diff --git a/.gitignore b/.gitignore index 796b96d..3709fc9 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ /build +/.nvim +.DS_Store +buildServer.json diff --git a/ImageViewer.xcodeproj/project.pbxproj b/ImageViewer.xcodeproj/project.pbxproj index 32f2eef..7a3a434 100644 --- a/ImageViewer.xcodeproj/project.pbxproj +++ b/ImageViewer.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 77; + objectVersion = 90; objects = { /* Begin PBXContainerItemProxy section */ @@ -63,24 +63,18 @@ /* Begin PBXFrameworksBuildPhase section */ 22FB3B892E18135A00A9B407 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; 22FB3B9F2E18135B00A9B407 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; 22FB3BA92E18135B00A9B407 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ @@ -118,14 +112,10 @@ ); buildRules = ( ); - dependencies = ( - ); fileSystemSynchronizedGroups = ( 22FB3B8E2E18135A00A9B407 /* ImageViewer */, ); name = ImageViewer; - packageProductDependencies = ( - ); productName = ImageViewer; productReference = 22FB3B8C2E18135A00A9B407 /* ImageViewer.app */; productType = "com.apple.product-type.application"; @@ -147,8 +137,6 @@ 22FB3BA52E18135B00A9B407 /* ImageViewerTests */, ); name = ImageViewerTests; - packageProductDependencies = ( - ); productName = ImageViewerTests; productReference = 22FB3BA22E18135B00A9B407 /* ImageViewerTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; @@ -170,8 +158,6 @@ 22FB3BAF2E18135B00A9B407 /* ImageViewerUITests */, ); name = ImageViewerUITests; - packageProductDependencies = ( - ); productName = ImageViewerUITests; productReference = 22FB3BAC2E18135B00A9B407 /* ImageViewerUITests.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; @@ -208,7 +194,7 @@ ); mainGroup = 22FB3B832E18135A00A9B407; minimizedProjectReferenceProxies = 1; - preferredProjectObjectVersion = 77; + preferredProjectObjectVersion = 90; productRefGroup = 22FB3B8D2E18135A00A9B407 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -223,48 +209,36 @@ /* Begin PBXResourcesBuildPhase section */ 22FB3B8A2E18135A00A9B407 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; 22FB3BA02E18135B00A9B407 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; 22FB3BAA2E18135B00A9B407 /* Resources */ = { isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 22FB3B882E18135A00A9B407 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; 22FB3B9E2E18135B00A9B407 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; 22FB3BA82E18135B00A9B407 /* Sources */ = { isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; files = ( ); - runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ @@ -282,7 +256,7 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - 22FB3BB62E18135B00A9B407 /* Debug */ = { + 22FB3BB62E18135B00A9B407 /* Debug configuration for PBXNativeTarget "ImageViewer" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -310,7 +284,7 @@ }; name = Debug; }; - 22FB3BB72E18135B00A9B407 /* Release */ = { + 22FB3BB72E18135B00A9B407 /* Release configuration for PBXNativeTarget "ImageViewer" */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; @@ -338,7 +312,7 @@ }; name = Release; }; - 22FB3BB82E18135B00A9B407 /* Debug */ = { + 22FB3BB82E18135B00A9B407 /* Debug configuration for PBXProject "ImageViewer" */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -401,7 +375,7 @@ }; name = Debug; }; - 22FB3BB92E18135B00A9B407 /* Release */ = { + 22FB3BB92E18135B00A9B407 /* Release configuration for PBXProject "ImageViewer" */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -457,7 +431,7 @@ }; name = Release; }; - 22FB3BBB2E18135B00A9B407 /* Debug */ = { + 22FB3BBB2E18135B00A9B407 /* Debug configuration for PBXNativeTarget "ImageViewerTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -475,7 +449,7 @@ }; name = Debug; }; - 22FB3BBC2E18135B00A9B407 /* Release */ = { + 22FB3BBC2E18135B00A9B407 /* Release configuration for PBXNativeTarget "ImageViewerTests" */ = { isa = XCBuildConfiguration; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; @@ -493,7 +467,7 @@ }; name = Release; }; - 22FB3BBE2E18135B00A9B407 /* Debug */ = { + 22FB3BBE2E18135B00A9B407 /* Debug configuration for PBXNativeTarget "ImageViewerUITests" */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; @@ -509,7 +483,7 @@ }; name = Debug; }; - 22FB3BBF2E18135B00A9B407 /* Release */ = { + 22FB3BBF2E18135B00A9B407 /* Release configuration for PBXNativeTarget "ImageViewerUITests" */ = { isa = XCBuildConfiguration; buildSettings = { CODE_SIGN_STYLE = Automatic; @@ -531,37 +505,33 @@ 22FB3B872E18135A00A9B407 /* Build configuration list for PBXProject "ImageViewer" */ = { isa = XCConfigurationList; buildConfigurations = ( - 22FB3BB82E18135B00A9B407 /* Debug */, - 22FB3BB92E18135B00A9B407 /* Release */, + 22FB3BB82E18135B00A9B407 /* Debug configuration for PBXProject "ImageViewer" */, + 22FB3BB92E18135B00A9B407 /* Release configuration for PBXProject "ImageViewer" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 22FB3BB52E18135B00A9B407 /* Build configuration list for PBXNativeTarget "ImageViewer" */ = { isa = XCConfigurationList; buildConfigurations = ( - 22FB3BB62E18135B00A9B407 /* Debug */, - 22FB3BB72E18135B00A9B407 /* Release */, + 22FB3BB62E18135B00A9B407 /* Debug configuration for PBXNativeTarget "ImageViewer" */, + 22FB3BB72E18135B00A9B407 /* Release configuration for PBXNativeTarget "ImageViewer" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 22FB3BBA2E18135B00A9B407 /* Build configuration list for PBXNativeTarget "ImageViewerTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 22FB3BBB2E18135B00A9B407 /* Debug */, - 22FB3BBC2E18135B00A9B407 /* Release */, + 22FB3BBB2E18135B00A9B407 /* Debug configuration for PBXNativeTarget "ImageViewerTests" */, + 22FB3BBC2E18135B00A9B407 /* Release configuration for PBXNativeTarget "ImageViewerTests" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 22FB3BBD2E18135B00A9B407 /* Build configuration list for PBXNativeTarget "ImageViewerUITests" */ = { isa = XCConfigurationList; buildConfigurations = ( - 22FB3BBE2E18135B00A9B407 /* Debug */, - 22FB3BBF2E18135B00A9B407 /* Release */, + 22FB3BBE2E18135B00A9B407 /* Debug configuration for PBXNativeTarget "ImageViewerUITests" */, + 22FB3BBF2E18135B00A9B407 /* Release configuration for PBXNativeTarget "ImageViewerUITests" */, ); - defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ diff --git a/ImageViewer/Info.plist b/ImageViewer/Info.plist index 0c67376..8d9449a 100644 --- a/ImageViewer/Info.plist +++ b/ImageViewer/Info.plist @@ -1,5 +1,10 @@ - + + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + diff --git a/ImageViewer/ViewController.swift b/ImageViewer/ViewController.swift index 454f714..8ea95c1 100644 --- a/ImageViewer/ViewController.swift +++ b/ImageViewer/ViewController.swift @@ -1,14 +1,245 @@ import UIKit -class ViewController: UIViewController { +enum PageTurn { + case next + case previous +} + +enum PageTurnMode { + case leftToRight + case rightToLeft + case scroll +} + +class ViewController: UIViewController, UIGestureRecognizerDelegate { + + var imageView = UIImageView() + var images: [UIImage] = [] + var page = 0 + var mode = PageTurnMode.leftToRight + var leftTap: UITapGestureRecognizer! + var rightTap: UITapGestureRecognizer! + var topTap: UITapGestureRecognizer! + let topBarView = UIView() + var isTopBarVisible = false + var topBarHeightConstraint: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() - view.backgroundColor = .white - let label = UILabel() - label.text = "Hello, UIKit!" - label.textAlignment = .center - label.frame = CGRect(x: 50, y: 150, width: 300, height: 50) - view.addSubview(label) + + view.backgroundColor = .red + + setup() + } + + func setup() { + setupImageView() + setupGestures() + setupTopBar() + } + + func setupGestures() { + let swipeLeft = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) + swipeLeft.direction = .left + view.addGestureRecognizer(swipeLeft) + + let swipeRight = UISwipeGestureRecognizer(target: self, action: #selector(handleSwipe(_:))) + swipeRight.direction = .right + view.addGestureRecognizer(swipeRight) + + setupTapZones() + } + + func setupTapZones() { + let leftView = UIView() + let rightView = UIView() + let topView = UIView() + + leftView.translatesAutoresizingMaskIntoConstraints = false + rightView.translatesAutoresizingMaskIntoConstraints = false + topView.translatesAutoresizingMaskIntoConstraints = false + + view.addSubview(leftView) + view.addSubview(rightView) + view.addSubview(topView) + + NSLayoutConstraint.activate([ + leftView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + leftView.topAnchor.constraint(equalTo: view.topAnchor), + leftView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + leftView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5), + + rightView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + rightView.topAnchor.constraint(equalTo: view.topAnchor), + rightView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + rightView.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.5), + + topView.topAnchor.constraint(equalTo: view.topAnchor), + topView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + topView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + topView.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.2), + ]) + + leftView.backgroundColor = .clear + rightView.backgroundColor = .clear + topView.backgroundColor = .clear + + leftTap = UITapGestureRecognizer(target: self, action: #selector(handleLeftTap)) + rightTap = UITapGestureRecognizer(target: self, action: #selector(handleRightTap)) + topTap = UITapGestureRecognizer(target: self, action: #selector(handleTopTap)) + + leftTap.delegate = self + rightTap.delegate = self + topTap.delegate = self + + leftView.addGestureRecognizer(leftTap) + rightView.addGestureRecognizer(rightTap) + topView.addGestureRecognizer(topTap) + } + + func setupTopBar() { + topBarView.translatesAutoresizingMaskIntoConstraints = false + topBarView.backgroundColor = UIColor.black.withAlphaComponent(0.8) // Or any style + view.addSubview(topBarView) + + topBarHeightConstraint = topBarView.heightAnchor.constraint(equalToConstant: 0) + + NSLayoutConstraint.activate([ + topBarView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), + topBarView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + topBarView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + topBarHeightConstraint, + ]) + } + + func gestureRecognizer( + _ gestureRecognizer: UIGestureRecognizer, + shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer + ) -> Bool { + if gestureRecognizer == leftTap || gestureRecognizer == rightTap, + otherGestureRecognizer == topTap + { + return true + } + return false + } + + func toggleTopBar() { + isTopBarVisible.toggle() + + topBarHeightConstraint.constant = isTopBarVisible ? 60 : 0 // You can adjust height + } + + @objc func handleTopTap() { + toggleTopBar() + } + + @objc func handleLeftTap() { + switch mode { + case .rightToLeft: changeImage(turn: .next) + case .leftToRight: changeImage(turn: .previous) + case .scroll: break + } + } + + @objc func handleRightTap() { + switch mode { + case .rightToLeft: changeImage(turn: .previous) + case .leftToRight: changeImage(turn: .next) + case .scroll: break + } + } + + @objc func handleSwipe(_ gesture: UISwipeGestureRecognizer) { + switch gesture.direction { + case .left: + switch mode { + case .rightToLeft: changeImage(turn: .previous) + case .leftToRight: changeImage(turn: .next) + case .scroll: break + } + case .right: + switch mode { + case .rightToLeft: changeImage(turn: .next) + case .leftToRight: changeImage(turn: .previous) + case .scroll: break + } + default: break + } + } + + func setupImageView() { + imageView.translatesAutoresizingMaskIntoConstraints = false + + imageView.contentMode = UIView.ContentMode.scaleAspectFit + + imageView.clipsToBounds = true + + view.addSubview(imageView) + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: view.topAnchor), + imageView.bottomAnchor.constraint(equalTo: view.bottomAnchor), + imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: view.trailingAnchor), + ]) + images = getImages() + imageView.image = images[0] + } + + func changeImage(turn: PageTurn) { + if images.count == 0 { + return + } + switch turn { + case .next: page = min(images.count - 1, page + 1) + case .previous: page = max(0, page - 1) + } + imageView.image = images[page] + } + + func getImages() -> [UIImage] { + let fileManager = FileManager.default + let supportedExtensions = ["png", "jpg", "jpeg"] + + guard + let documentsURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first + else { + print("Documents directory not found.") + return [] + } + + do { + let contents = try fileManager.contentsOfDirectory( + at: documentsURL, includingPropertiesForKeys: nil) + + var images: [UIImage] = [] + + for file in contents { + if supportedExtensions.contains(file.pathExtension.lowercased()) { + print("Loading image: \(file.lastPathComponent)") + if let image = UIImage(contentsOfFile: file.path) { + images.append(image) + } else { + print("Failed to load image.") + } + } else { + print("No image files found in Documents directory.") + } + } + return images + } catch { + print("Error reading contents of Documents directory: \(error)") + } + return [] + } + func createTestFile() { + if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + .first + { + let fileURL = documentsURL.appendingPathComponent("dummy.txt") + let data = ".".data(using: .utf8)! + try? data.write(to: fileURL) + } } } diff --git a/run.sh b/run.sh index 9a1841a..442b153 100755 --- a/run.sh +++ b/run.sh @@ -7,6 +7,8 @@ DERIVED_DATA="./build" SIMULATOR='iOS Simulator' COMPANY='ImageViewer' +sudo bash -c "echo '127.0.0.1 developerservices2.apple.com' >>/etc/hosts" + # Boot and launch simulator xcrun simctl boot "$SIM_DEVICE" open -a Simulator @@ -18,3 +20,5 @@ xcodebuild -scheme "$SCHEME" -derivedDataPath "$DERIVED_DATA" -destination "plat APP_PATH="$DERIVED_DATA/Build/Products/Debug-iphonesimulator/$APP_NAME.app" xcrun simctl install booted "$APP_PATH" xcrun simctl launch booted "$COMPANY.$APP_NAME" + +sudo sed -i '' '/developerservices2\.apple\.com/d' /etc/hosts