diff --git a/ios/Sources/RootView.swift b/ios/Sources/RootView.swift
index 39419a9..3b673ae 100644
--- a/ios/Sources/RootView.swift
+++ b/ios/Sources/RootView.swift
@@ -4,6 +4,8 @@ struct RootView: View {
@EnvironmentObject private var session: Session
@EnvironmentObject private var router: Router
@EnvironmentObject private var display: DisplaySettings
+ /// Über einen gescannten Universal Link geöffnetes Einzelstück.
+ @State private var linkedItem: Item?
var body: some View {
Group {
@@ -41,6 +43,14 @@ struct RootView: View {
}
}
}
+ .task(id: router.openItemUid) {
+ guard let uid = router.openItemUid else { return }
+ linkedItem = try? await APIClient.shared.itemByUid(uid: uid)
+ router.openItemUid = nil
+ }
+ .sheet(item: $linkedItem) { it in
+ NavigationStack { ItemEditView(item: it) }
+ }
}
}
diff --git a/ios/Sources/Vorrania.entitlements b/ios/Sources/Vorrania.entitlements
new file mode 100644
index 0000000..4e3230e
--- /dev/null
+++ b/ios/Sources/Vorrania.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.developer.associated-domains
+
+ applinks:vorrania-ch.scarriffle.com
+
+
+
diff --git a/ios/Sources/VorraniaApp.swift b/ios/Sources/VorraniaApp.swift
index 08e4370..5b9648d 100644
--- a/ios/Sources/VorraniaApp.swift
+++ b/ios/Sources/VorraniaApp.swift
@@ -15,6 +15,8 @@ enum Route: String {
final class Router: ObservableObject {
@Published var route: Route?
+ /// Über einen Universal Link (…/i/) zu öffnendes Einzelstück.
+ @Published var openItemUid: String?
func handle(shortcut type: String) {
if type.hasSuffix("checkin") { route = .checkin }
@@ -28,11 +30,18 @@ final class Router: ObservableObject {
route = .expiring
}
- /// vorrania://checkin bzw. vorrania://checkout
+ /// vorrania://checkin bzw. vorrania://checkout – oder ein Universal Link
+ /// (https://…/i/), der das Einzelstück direkt öffnet.
func handle(url: URL) {
- guard url.scheme == "vorrania" else { return }
- let target = (url.host ?? url.path.replacingOccurrences(of: "/", with: "")).lowercased()
- if let route = Route(rawValue: target) { self.route = route }
+ if url.scheme == "vorrania" {
+ let target = (url.host ?? url.path.replacingOccurrences(of: "/", with: "")).lowercased()
+ if let route = Route(rawValue: target) { self.route = route }
+ return
+ }
+ let parts = url.pathComponents // z.B. ["/", "i", "ABC123"]
+ if let idx = parts.firstIndex(of: "i"), idx + 1 < parts.count {
+ openItemUid = parts[idx + 1].uppercased()
+ }
}
}
@@ -51,6 +60,9 @@ struct VorraniaApp: App {
.environmentObject(router)
.environmentObject(display)
.onOpenURL { router.handle(url: $0) }
+ .onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { activity in
+ if let url = activity.webpageURL { router.handle(url: url) }
+ }
.onAppear {
if let type = AppDelegate.pendingShortcut {
router.handle(shortcut: type)
diff --git a/ios/Vorrania.xcodeproj/project.pbxproj b/ios/Vorrania.xcodeproj/project.pbxproj
index 6f982ef..4fa6564 100644
--- a/ios/Vorrania.xcodeproj/project.pbxproj
+++ b/ios/Vorrania.xcodeproj/project.pbxproj
@@ -80,6 +80,7 @@
C35D34107C8CBB8B7C6C6650 /* LocalOverview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalOverview.swift; sourceTree = ""; };
CDD672F65352DA0D122EC9AE /* CategoryPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CategoryPicker.swift; sourceTree = ""; };
DC0DDD06332E567E70CA842F /* CheckOutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckOutView.swift; sourceTree = ""; };
+ DE4C9D922F5AC05270B1CF9B /* Vorrania.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Vorrania.entitlements; sourceTree = ""; };
E580BA2815BB8F6341D64B47 /* ChartCards.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChartCards.swift; sourceTree = ""; };
E6E6139CCD0B6C51B3919EE9 /* DashboardView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DashboardView.swift; sourceTree = ""; };
EA706060734632DE78FA1073 /* ServerListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ServerListView.swift; sourceTree = ""; };
@@ -123,6 +124,7 @@
EA706060734632DE78FA1073 /* ServerListView.swift */,
121D44B9990DA931A9CD5847 /* ServerProfiles.swift */,
AD2F9406BD0D4F0D30FED345 /* Session.swift */,
+ DE4C9D922F5AC05270B1CF9B /* Vorrania.entitlements */,
9948219CDDC4188EA4298F22 /* VorraniaApp.swift */,
);
path = Sources;
@@ -317,6 +319,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = Sources/Vorrania.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = PP34X97WS3;
@@ -338,6 +341,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
+ CODE_SIGN_ENTITLEMENTS = Sources/Vorrania.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
CODE_SIGN_STYLE = Automatic;
DEVELOPMENT_TEAM = PP34X97WS3;
diff --git a/ios/project.yml b/ios/project.yml
index fc7b445..9a3d45a 100644
--- a/ios/project.yml
+++ b/ios/project.yml
@@ -39,3 +39,11 @@ targets:
TARGETED_DEVICE_FAMILY: "1,2"
SWIFT_VERSION: "5.9"
GENERATE_INFOPLIST_FILE: NO
+ # Universal Links: …/i/ öffnet direkt die App. Dafür muss im
+ # Apple-Developer-Portal die Capability "Associated Domains" für die App-ID
+ # aktiv sein (einmalig), sonst schlägt das Signieren fehl.
+ entitlements:
+ path: Sources/Vorrania.entitlements
+ properties:
+ com.apple.developer.associated-domains:
+ - applinks:vorrania-ch.scarriffle.com
diff --git a/web/nginx.conf b/web/nginx.conf
index 58719b3..7fff941 100644
--- a/web/nginx.conf
+++ b/web/nginx.conf
@@ -9,6 +9,14 @@ server {
# den Upload ueberhaupt sieht.
client_max_body_size 12m;
+ # Universal Links: iOS holt diese Datei, um /i/-Links direkt in der App
+ # zu öffnen. Bewusst nur die Einzelstück-Pfade (/i/*); Lagerort-Links (/l/*)
+ # bleiben im Browser. Team-/Bundle-ID aus ios/project.yml.
+ location = /.well-known/apple-app-site-association {
+ default_type application/json;
+ return 200 '{"applinks":{"details":[{"appIDs":["PP34X97WS3.com.scarriffle.vorrania"],"components":[{"/":"/i/*"}]}]}}';
+ }
+
# SPA-Routing: unbekannte Pfade auf index.html mappen
location / {
try_files $uri $uri/ /index.html;