Fix profile save file not working

SwiftUI only supports one fileExporter modifier per view hierarchy.
When multiple fileExporter modifiers are attached, only the last one
takes effect. Unified ProfileExportDocument and ProfileJSONExportDocument
into a single ProfileAnyExportDocument to use one fileExporter for both
file types.
This commit is contained in:
世界
2026-01-13 17:05:12 +08:00
parent a393ff4ca8
commit 42d1107c53
3 changed files with 81 additions and 116 deletions
@@ -104,23 +104,12 @@ public struct ProfileCard: View {
}
}
.fileExporter(
isPresented: $viewModel.showProfileExporter,
document: viewModel.profileExportDocument,
contentType: .profile,
defaultFilename: viewModel.profileExportDocument?.filename
isPresented: $viewModel.showExporter,
document: viewModel.exportDocument,
contentType: viewModel.exportDocument?.contentType ?? .data,
defaultFilename: viewModel.exportDocument?.filename
) { result in
viewModel.profileExportDocument = nil
if case let .failure(error) = result {
viewModel.alert = AlertState(error: error)
}
}
.fileExporter(
isPresented: $viewModel.showJSONExporter,
document: viewModel.profileJSONExportDocument,
contentType: .json,
defaultFilename: viewModel.profileJSONExportDocument?.filename
) { result in
viewModel.profileJSONExportDocument = nil
viewModel.exportDocument = nil
if case let .failure(error) = result {
viewModel.alert = AlertState(error: error)
}
@@ -371,18 +360,13 @@ public struct ProfileCard: View {
do {
switch type {
case .file:
viewModel.profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
let doc = try ProfileExportDocument(content: profile.origin.toContent())
viewModel.exportDocument = ProfileAnyExportDocument(profile: doc)
case .json:
viewModel.profileJSONExportDocument = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
}
DispatchQueue.main.async {
switch type {
case .file:
viewModel.showProfileExporter = true
case .json:
viewModel.showJSONExporter = true
}
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
viewModel.exportDocument = ProfileAnyExportDocument(json: doc)
}
viewModel.showExporter = true
} catch {
viewModel.alert = AlertState(error: error)
}
@@ -495,10 +479,8 @@ extension ProfileCard {
@Published var profileToEdit: Profile?
@Published var shareItemType: ShareItemType?
#if !os(tvOS)
@Published var profileExportDocument: ProfileExportDocument?
@Published var showProfileExporter = false
@Published var profileJSONExportDocument: ProfileJSONExportDocument?
@Published var showJSONExporter = false
@Published var exportDocument: ProfileAnyExportDocument?
@Published var showExporter = false
#endif
#if os(macOS)
var shareButtonView: NSView?
@@ -532,17 +532,11 @@ private struct ProfilePickerRow: View {
#if os(macOS)
@State private var shareItemType: ShareItemType?
@State private var exportItemType: ExportItemType?
@State private var profileExportDocument: ProfileExportDocument?
@State private var showProfileExporter = false
@State private var profileJSONExportDocument: ProfileJSONExportDocument?
@State private var showJSONExporter = false
@State private var menuAnchorView: NSView?
#endif
#if os(iOS)
@State private var profileExportDocument: ProfileExportDocument?
@State private var showProfileExporter = false
@State private var profileJSONExportDocument: ProfileJSONExportDocument?
@State private var showJSONExporter = false
#if !os(tvOS)
@State private var exportDocument: ProfileAnyExportDocument?
@State private var showExporter = false
#endif
var body: some View {
@@ -722,23 +716,12 @@ private struct ProfilePickerRow: View {
}
}
.fileExporter(
isPresented: $showProfileExporter,
document: profileExportDocument,
contentType: .profile,
defaultFilename: profileExportDocument?.filename
isPresented: $showExporter,
document: exportDocument,
contentType: exportDocument?.contentType ?? .data,
defaultFilename: exportDocument?.filename
) { result in
profileExportDocument = nil
if case let .failure(error) = result {
alert = AlertState(error: error)
}
}
.fileExporter(
isPresented: $showJSONExporter,
document: profileJSONExportDocument,
contentType: .json,
defaultFilename: profileJSONExportDocument?.filename
) { result in
profileJSONExportDocument = nil
exportDocument = nil
if case let .failure(error) = result {
alert = AlertState(error: error)
}
@@ -772,23 +755,12 @@ private struct ProfilePickerRow: View {
}
}
.fileExporter(
isPresented: $showProfileExporter,
document: profileExportDocument,
contentType: .profile,
defaultFilename: profileExportDocument?.filename
isPresented: $showExporter,
document: exportDocument,
contentType: exportDocument?.contentType ?? .data,
defaultFilename: exportDocument?.filename
) { result in
profileExportDocument = nil
if case let .failure(error) = result {
alert = AlertState(error: error)
}
}
.fileExporter(
isPresented: $showJSONExporter,
document: profileJSONExportDocument,
contentType: .json,
defaultFilename: profileJSONExportDocument?.filename
) { result in
profileJSONExportDocument = nil
exportDocument = nil
if case let .failure(error) = result {
alert = AlertState(error: error)
}
@@ -985,18 +957,13 @@ private struct ProfilePickerRow: View {
do {
switch type {
case .file:
profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
let doc = try ProfileExportDocument(content: profile.origin.toContent())
exportDocument = ProfileAnyExportDocument(profile: doc)
case .json:
profileJSONExportDocument = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
}
DispatchQueue.main.async {
switch type {
case .file:
showProfileExporter = true
case .json:
showJSONExporter = true
}
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
exportDocument = ProfileAnyExportDocument(json: doc)
}
showExporter = true
} catch {
alert = AlertState(error: error)
}
@@ -1052,12 +1019,13 @@ private struct ProfilePickerRow: View {
do {
switch type {
case .file:
profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
showProfileExporter = true
let doc = try ProfileExportDocument(content: profile.origin.toContent())
exportDocument = ProfileAnyExportDocument(profile: doc)
case .json:
profileJSONExportDocument = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
showJSONExporter = true
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
exportDocument = ProfileAnyExportDocument(json: doc)
}
showExporter = true
} catch {
alert = AlertState(error: error)
}
@@ -1167,10 +1135,8 @@ private struct ProfilePickerRow: View {
@State private var isUpdating = false
@State private var showQRCode = false
@State private var showQRSShare = false
@State private var profileExportDocument: ProfileExportDocument?
@State private var showProfileExporter = false
@State private var profileJSONExportDocument: ProfileJSONExportDocument?
@State private var showJSONExporter = false
@State private var exportDocument: ProfileAnyExportDocument?
@State private var showExporter = false
var body: some View {
Group {
@@ -1234,23 +1200,12 @@ private struct ProfilePickerRow: View {
}
}
.fileExporter(
isPresented: $showProfileExporter,
document: profileExportDocument,
contentType: .profile,
defaultFilename: profileExportDocument?.filename
isPresented: $showExporter,
document: exportDocument,
contentType: exportDocument?.contentType ?? .data,
defaultFilename: exportDocument?.filename
) { result in
profileExportDocument = nil
if case let .failure(error) = result {
alert = AlertState(error: error)
}
}
.fileExporter(
isPresented: $showJSONExporter,
document: profileJSONExportDocument,
contentType: .json,
defaultFilename: profileJSONExportDocument?.filename
) { result in
profileJSONExportDocument = nil
exportDocument = nil
if case let .failure(error) = result {
alert = AlertState(error: error)
}
@@ -1344,18 +1299,13 @@ private struct ProfilePickerRow: View {
do {
switch type {
case .file:
profileExportDocument = try ProfileExportDocument(content: profile.origin.toContent())
let doc = try ProfileExportDocument(content: profile.origin.toContent())
exportDocument = ProfileAnyExportDocument(profile: doc)
case .json:
profileJSONExportDocument = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
}
DispatchQueue.main.async {
switch type {
case .file:
showProfileExporter = true
case .json:
showJSONExporter = true
}
let doc = try ProfileJSONExportDocument(jsonContent: profile.origin.read(), name: profile.name)
exportDocument = ProfileAnyExportDocument(json: doc)
}
showExporter = true
} catch {
alert = AlertState(error: error)
}
+34 -1
View File
@@ -115,7 +115,7 @@ public struct TypedProfile: Transferable, Codable {
}
public extension UTType {
static var profile: UTType { .init(exportedAs: AppConfiguration.profileUTType) }
static let profile = UTType(exportedAs: AppConfiguration.profileUTType)
}
#if !os(tvOS)
@@ -177,4 +177,37 @@ public extension UTType {
return FileWrapper(regularFileWithContents: data)
}
}
public struct ProfileAnyExportDocument: FileDocument {
public static var readableContentTypes: [UTType] { [.data, .json] }
public let data: Data
public let filename: String
public let contentType: UTType
public init(profile: ProfileExportDocument) {
data = profile.data
filename = profile.filename
contentType = .data
}
public init(json: ProfileJSONExportDocument) {
data = json.content.data(using: .utf8) ?? Data()
filename = json.filename
contentType = .json
}
public init(configuration: ReadConfiguration) throws {
guard let data = configuration.file.regularFileContents else {
throw CocoaError(.fileReadCorruptFile)
}
self.data = data
filename = "profile"
contentType = .data
}
public func fileWrapper(configuration _: WriteConfiguration) throws -> FileWrapper {
FileWrapper(regularFileWithContents: data)
}
}
#endif