Skip to content

Commit 072de83

Browse files
committed
ActionUIViewer: Add support for remote json
Download json and then open when the json location argument is http:// or https:// Fix a crash when loading VideoPlayer. For some reason `import AVKit` in VideoPlayer.swift in ActionUI library is not auto-linking it in ActionUIViewer
1 parent 05f6604 commit 072de83

2 files changed

Lines changed: 70 additions & 18 deletions

File tree

ActionUI.xcodeproj/project.pbxproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
1DDC4BE32F3DA8A100604C66 /* ActionUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D03F8E62E49EEC3004A42BB /* ActionUI.framework */; };
2020
1DDC4BF32F3DC8B700604C66 /* ActionUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1D03F8E62E49EEC3004A42BB /* ActionUI.framework */; };
2121
1DDC4BF42F3DC8B700604C66 /* ActionUISwiftAdapter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DD4E4282E8F3228001E0C83 /* ActionUISwiftAdapter.framework */; };
22+
1DDC4C4A2F40684500604C66 /* AVKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1DDC4C492F40684500604C66 /* AVKit.framework */; };
2223
/* End PBXBuildFile section */
2324

2425
/* Begin PBXContainerItemProxy section */
@@ -176,6 +177,7 @@
176177
1DD4E44F2E8FA9CA001E0C83 /* ActionUIObjCTestApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ActionUIObjCTestApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
177178
1DDC483C2F3DA52D00604C66 /* ActionUIVerifier */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ActionUIVerifier; sourceTree = BUILT_PRODUCTS_DIR; };
178179
1DDC4BE82F3DC89D00604C66 /* ActionUIViewer */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = ActionUIViewer; sourceTree = BUILT_PRODUCTS_DIR; };
180+
1DDC4C492F40684500604C66 /* AVKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVKit.framework; path = System/Library/Frameworks/AVKit.framework; sourceTree = SDKROOT; };
179181
/* End PBXFileReference section */
180182

181183
/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */
@@ -399,6 +401,7 @@
399401
isa = PBXFrameworksBuildPhase;
400402
buildActionMask = 2147483647;
401403
files = (
404+
1DDC4C4A2F40684500604C66 /* AVKit.framework in Frameworks */,
402405
1DDC4BF32F3DC8B700604C66 /* ActionUI.framework in Frameworks */,
403406
1DDC4BF42F3DC8B700604C66 /* ActionUISwiftAdapter.framework in Frameworks */,
404407
);
@@ -452,6 +455,7 @@
452455
1D03F97A2E49F68F004A42BB /* Frameworks */ = {
453456
isa = PBXGroup;
454457
children = (
458+
1DDC4C492F40684500604C66 /* AVKit.framework */,
455459
1D03F97D2E49F69A004A42BB /* SwiftUI.framework */,
456460
);
457461
name = Frameworks;

ActionUIViewer/ActionUIViewer.swift

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
// ActionUI - SwiftUI component library
2+
// Copyright (c) 2025-2026 Tomasz Kukielka
13
//
2-
// ActionUIViewer main.swift
4+
// Licensed under the PolyForm Small Business License 1.0.0
5+
// https://polyformproject.org/licenses/small-business/1.0.0
6+
7+
//
8+
// ActionUIViewer.swift
39
// ActionUIViewer
410
//
511
// A command-line tool to view ActionUI JSON files in a window.
6-
// Usage: ActionUIViewer <path-to-json-file>
712
//
813

914
import SwiftUI
@@ -42,6 +47,13 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
4247
var screenshotPath: String?
4348

4449
func applicationDidFinishLaunching(_ notification: Notification) {
50+
Task { @MainActor in
51+
await handleApplicationLaunch()
52+
}
53+
}
54+
55+
@MainActor
56+
func handleApplicationLaunch() async {
4557
var jsonFilePath: String?
4658
var screenshotPath: String?
4759

@@ -62,27 +74,63 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
6274
i += 1
6375
}
6476

65-
guard let path = jsonFilePath else {
66-
print("Usage: ActionUIViewer [--screenshot <output.png>] <input.json>")
77+
guard let pathOrUrl = jsonFilePath else {
78+
print("Usage: ActionUIViewer [--screenshot <output.png>] <path/to/input.json|https://url.to/input.json>")
6779
NSApp.terminate(nil)
6880
return
6981
}
7082

71-
let url = URL(fileURLWithPath: path)
72-
self.screenshotPath = screenshotPath
83+
var url: URL
84+
var displayTitle: String
7385

74-
guard FileManager.default.fileExists(atPath: path) else {
75-
print("Error: File not found at path: \(path)")
76-
window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 400, height: 200),
77-
styleMask: [.titled, .closable], backing: .buffered, defer: false)
78-
window.contentView = NSHostingView(rootView: ErrorView(message: "File not found:\n\(path)"))
79-
window.delegate = self
80-
window.center()
81-
window.makeKeyAndOrderFront(nil)
82-
NSApp.activate(ignoringOtherApps: true)
83-
return
86+
if pathOrUrl.hasPrefix("http://") || pathOrUrl.hasPrefix("https://") {
87+
guard let parsedUrl = URL(string: pathOrUrl) else {
88+
print("Error: Invalid URL: \(pathOrUrl)")
89+
NSApp.terminate(nil)
90+
return
91+
}
92+
url = parsedUrl
93+
displayTitle = parsedUrl.lastPathComponent.isEmpty ? parsedUrl.host ?? parsedUrl.absoluteString : parsedUrl.lastPathComponent
94+
95+
print("Fetching remote JSON...")
96+
97+
do {
98+
let (data, response) = try await URLSession.shared.data(from: url)
99+
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {
100+
print("Error: Failed to fetch remote JSON")
101+
NSApp.terminate(nil)
102+
return
103+
}
104+
105+
let tempDir = FileManager.default.temporaryDirectory
106+
let tempFile = tempDir.appendingPathComponent("remote_\(UUID().uuidString).json")
107+
try data.write(to: tempFile)
108+
url = tempFile
109+
print("Saved remote JSON to temp file: \(tempFile.path)")
110+
} catch {
111+
print("Error fetching remote JSON: \(error)")
112+
NSApp.terminate(nil)
113+
return
114+
}
115+
} else {
116+
url = URL(fileURLWithPath: pathOrUrl)
117+
displayTitle = url.lastPathComponent
118+
119+
guard FileManager.default.fileExists(atPath: pathOrUrl) else {
120+
print("Error: File not found at path: \(pathOrUrl)")
121+
window = NSWindow(contentRect: NSRect(x: 0, y: 0, width: 400, height: 200),
122+
styleMask: [.titled, .closable], backing: .buffered, defer: false)
123+
window.contentView = NSHostingView(rootView: ErrorView(message: "File not found:\n\(pathOrUrl)"))
124+
window.delegate = self
125+
window.center()
126+
window.makeKeyAndOrderFront(nil)
127+
NSApp.activate(ignoringOtherApps: true)
128+
return
129+
}
84130
}
85131

132+
self.screenshotPath = screenshotPath
133+
86134
let windowUUID = UUID().uuidString
87135
let logger = CustomLogger()
88136
ActionUISwift.setLogger(logger)
@@ -93,7 +141,7 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
93141
window = NSWindow(contentRect: NSRect(x: 100, y: 100, width: 800, height: 600),
94142
styleMask: [.titled, .closable, .miniaturizable, .resizable],
95143
backing: .buffered, defer: false)
96-
window.title = "ActionUI Viewer - \(url.lastPathComponent)"
144+
window.title = "ActionUI Viewer - \(displayTitle)"
97145
window.center()
98146
window.delegate = self
99147

@@ -123,7 +171,7 @@ class ActionUIViewerAppDelegate: NSObject, NSApplicationDelegate, NSWindowDelega
123171
return
124172
}
125173

126-
let image = CGWindowListCreateImage(CGRect.zero, .optionIncludingWindow, CGWindowID(windowNumber), .boundsIgnoreFraming)
174+
let image = CGWindowListCreateImage(CGRect.zero, .optionIncludingWindow, CGWindowID(windowNumber), [])
127175
guard let cgImage = image else {
128176
print("Error: Failed to capture window")
129177
return

0 commit comments

Comments
 (0)