An iOS app for quickly scanning paper documents with the camera and automatically saving/uploading the result wherever you need it — Photos, the Files app, a WebDAV server, and/or an SMB share, all at once if you like.
- Document scanning powered by VisionKit's document camera (
VNDocumentCameraScan), with automatic edge detection, perspective correction, and multi-page capture. - On-device OCR (Vision framework) analyzes the first page of every scan to suggest a document name and date (e.g. detecting "Rechnung", "Invoice", "Kassenbon", ...), which is used to pre-fill the export filename.
- PDF generation — scanned pages are combined into a single PDF, each page scaled to a consistent A4 width while preserving its original aspect ratio.
- Multiple save/upload destinations, any combination of which can be enabled and run
automatically after every scan:
- Photos — saves each page as an image to the photo library.
- Files — saves the PDF to a folder in the Files app (either a user-picked folder via a security-scoped bookmark, or the app's own Documents folder by default).
- WebDAV — uploads the PDF via HTTP
PUTto one or more configured WebDAV servers. - SMB — uploads the PDF to one or more configured SMB (Samba) shares, using the AMSMB2 library.
- A manual "…" menu on the scan preview also lets you Share, Save to Photos, or Save to Files on demand, regardless of the auto-save settings.
- Multiple servers per protocol — configure several WebDAV/SMB destinations and either upload to all of them or just a chosen default.
- Secure credential storage — server metadata (name, URL/host, username, ...) is
persisted in
UserDefaultsas JSON; passwords are stored separately in the iOS Keychain and are never written to disk in plain text or included in JSON exports. - Localized in English and German (
Localizable.xcstrings, a Swift String Catalog).
- macOS with Xcode 16 or newer (the project targets iOS 18.6+).
- XcodeGen to generate the
.xcodeproj— install via Homebrew:brew install xcodegen
- An Apple Developer Team ID if you want to run on a physical device (see Configuration below).
This project's .xcodeproj is generated from project.yml using
XcodeGen — WhateverScanner.xcodeproj/project.pbxproj is a build artifact and
should never be hand-edited or committed to manually.
project.yml XcodeGen project specification (targets, settings, sources)
WhateverScanner/
WhateverScannerApp.swift App entry point
ContentView.swift Root view (launch screen → onboarding or main view)
Info.plist
Localizable.xcstrings String Catalog (en/de)
Models/ AppSettings, WebDAVServer, SMBServer
Services/ KeychainService, PDFService, OCRService, FilesService,
PhotosService, WebDAVService, SMBService
Views/
Main/ Main scanning screen
Scanning/ Scanner, scan preview, document export, share sheet
Settings/ Settings screen, add-server forms, folder picker
Onboarding/LaunchScreen First-run and launch UI
WhateverScannerTests/ Unit tests (one file per Model/Service)
- Clone the repository:
git clone https://github.com/marchein/WhateverScanner.git cd WhateverScanner - Generate the Xcode project:
xcodegen generate
- Open the generated project:
open WhateverScanner.xcodeproj
- Build and run the
WhateverScannerscheme on a simulator or device.
Important: whenever you add, remove, or rename Swift files, or change
project.yml(targets, build settings, dependencies, ...), re-runxcodegen generatebefore building — the.xcodeprojis not kept in sync automatically, and files won't be picked up otherwise.
WhateverScanner.xcodeproj/ is a generated build artifact and is git-ignored,
not committed — everyone (and every CI system) regenerates it locally from
project.yml rather than relying on a potentially stale, committed copy.
- Signing — the project uses automatic code signing
(
CODE_SIGN_STYLE: Automatic) with a team ID set in project.yml undersettings.DEVELOPMENT_TEAM. Replace it with your own team ID to build to a physical device, or switch Xcode's signing settings after generating the project. - Bundle identifier — set per-target in
project.yml(PRODUCT_BUNDLE_IDENTIFIER), currentlyde.marc-hein.whateverscanner. - Dependencies — Swift Package Manager dependencies are declared under the
top-level
packages:key inproject.yml(currently just AMSMB2 for SMB support) and referenced per-target underdependencies:.
Since the .xcodeproj isn't committed, Xcode Cloud needs to generate it itself
before every build. This is handled by ci_scripts/ci_post_clone.sh,
an Xcode Cloud post-clone script
that installs XcodeGen via Homebrew and runs xcodegen generate against the
freshly cloned repository before Xcode Cloud resolves/builds the project.
Xcode Cloud's own package-resolution step runs with automatic dependency
resolution disabled and requires a Package.resolved to already exist —
but the workspace is freshly generated on every run, so there's nothing to
find yet, and even invoking xcodebuild -resolvePackageDependencies
ourselves in the script is blocked by that same restriction. Instead,
ci_scripts/Package.resolved is committed as a
template and the post-clone script copies it into the generated workspace's
project.xcworkspace/xcshareddata/swiftpm/ folder. Its originHash is
derived from the package requirements declared in project.yml and stays
stable as long as those don't change, so it survives being regenerated.
Whenever you add, remove, or bump a version of a package in
project.yml'spackages:section, regenerate this template locally and commit the update:xcodegen generate xcodebuild -resolvePackageDependencies -project WhateverScanner.xcodeproj -scheme WhateverScanner cp WhateverScanner.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved ci_scripts/Package.resolved
xcodebuild -project WhateverScanner.xcodeproj -scheme WhateverScanner \
-destination 'generic/platform=iOS Simulator' -skipMacroValidation buildUnit tests live in WhateverScannerTests/, organized one file per Model/Service
(e.g. PDFServiceTests.swift, AppSettingsTests.swift, KeychainServiceTests.swift, ...).
Run them from Xcode (⌘U) or from the command line:
xcodebuild test -project WhateverScanner.xcodeproj -scheme WhateverScanner \
-destination 'platform=iOS Simulator,name=iPhone 17'Network-dependent services (WebDAVService, SMBService) and system-permission
flows (PhotosService) have no injectable transport/mocking layer, so only their
deterministic, network-free code paths (input validation, error descriptions) are
covered by unit tests — not live uploads or permission prompts.
User-facing strings live in WhateverScanner/Localizable.xcstrings, a Swift
String Catalog supporting English (source language) and German. Edit it directly
in Xcode's String Catalog editor, which keeps translation state (new,
translated, ...) in sync.
No license has been specified for this project yet.