add bar, gestures for switching between images

This commit is contained in:
2025-07-07 19:56:42 +02:00
parent d8e5cb2370
commit 6d541f9798
5 changed files with 269 additions and 56 deletions

3
.gitignore vendored
View File

@@ -1 +1,4 @@
/build
/.nvim
.DS_Store
buildServer.json

View File

@@ -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 */

View File

@@ -1,5 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict/>
<dict>
<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
</dict>
</plist>

View File

@@ -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)
}
}
}

4
run.sh
View File

@@ -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