Bug Fix: Program Configuration View Freezes (#1345)

* Quick FIx (#1343)

* Fixes App Freeze on Program Configuration view by removing `.toolbar` method (side effect: Program icon removed from top bar).
 - modifying toolbar inside a NavigationStack somehow causes swiftUI into an infinite update loop.

* Mitigated new SwiftLint rule #5263 (Prefer non-optional UTF8 String <-> Data conversion)

* Quick FIx (#1343) [Amend 1]

* Fixes App Freeze on Program Configuration view by removing `.toolbar` method (side effect: Program icon removed from top bar).
 - modifying toolbar inside a NavigationStack somehow causes swiftUI into an infinite update loop.

* Mitigated new SwiftLint rule #5263 (Prefer non-optional UTF8 String <-> Data conversion)

* Mitigated new SwiftLint rule #5845 (Optional Data -> String Conversion Violation: Prefer failable `String(bytes:encoding:)` initializer)

)

* Quick FIx (#1343) [Amend 2]

* Fixes App Freeze on Program Configuration view by removing `.toolbar` method (side effect: Program icon removed from top bar).
 - modifying toolbar inside a NavigationStack somehow causes swiftUI into an infinite update loop.

* Mitigated new SwiftLint rule #5263 (Prefer non-optional UTF8 String <-> Data conversion)

* Mitigated new SwiftLint rule #5845 (Optional Data -> String Conversion Violation: Prefer failable `String(bytes:encoding:)` initializer)

)

* Bug FIx (#1343)

* Fixes App Freeze on Program Configuration view by reimplementing program icon toolbar item.
 - `ToolbarItem` is uniquely identifiable and probably cached by SwiftUI internals, mutating its content / use a conflicting id will cause unexpected problems.
 - Now a new `ToolbarItem` will be created when its content updates.

* Removed thread creation when fetching program icons
 - `.task` itself is async.

* Mitigated new SwiftLint rules

* Bug Fix (#1343)

* Rectified a few spacing / indentation

=== Amended Commit ===
* Fixes App Freeze on Program Configuration view by reimplementing program icon toolbar item.
 - `ToolbarItem` is uniquely identifiable and probably cached by SwiftUI internals, mutating its content / use a conflicting id will cause unexpected problems.
 - Now a new `ToolbarItem` will be created when its content updates.

* Removed thread creation when fetching program icons
 - `.task` itself is async.

* Mitigated new SwiftLint rules
This commit is contained in:
UeharaYou
2025-04-05 14:35:33 +09:00
committed by GitHub
parent ce04b2503d
commit dbe1142463
6 changed files with 52 additions and 48 deletions
+9 -9
View File
@@ -81,19 +81,19 @@ class Winetricks {
}
static func parseVerbs() async -> [WinetricksCategory] {
var verbs: String?
// Grab the verbs file
let verbsURL = WhiskyWineInstaller.libraryFolder.appending(path: "verbs.txt")
do {
let (data, _) = try await URLSession.shared.data(from: verbsURL)
verbs = String(data: data, encoding: .utf8)
} catch {
return []
}
let verbs: String = await { () async -> String in
do {
let (data, _) = try await URLSession.shared.data(from: verbsURL)
return String(data: data, encoding: .utf8) ?? String()
} catch {
return String()
}
}()
// Read the file line by line
let lines = verbs?.components(separatedBy: "\n") ?? [""]
let lines = verbs.components(separatedBy: "\n")
var categories: [WinetricksCategory] = []
var currentCategory: WinetricksCategory?
+19 -23
View File
@@ -22,8 +22,8 @@ import UniformTypeIdentifiers
struct ProgramView: View {
@ObservedObject var program: Program
@State var image: Image?
@State var programLoading: Bool = false
@State var cachedIconImage: Image?
@AppStorage("configSectionExapnded") private var configSectionExpanded: Bool = true
@AppStorage("envArgsSectionExpanded") private var envArgsSectionExpanded: Bool = true
@@ -48,9 +48,6 @@ struct ProgramView: View {
}
EnvironmentArgView(program: program, isExpanded: $envArgsSectionExpanded)
}
.formStyle(.grouped)
.animation(.whiskyDefault, value: configSectionExpanded)
.animation(.whiskyDefault, value: envArgsSectionExpanded)
.bottomBar {
HStack {
Spacer()
@@ -92,30 +89,29 @@ struct ProgramView: View {
}
.padding()
}
.navigationTitle(program.name)
.toolbar {
ToolbarItem(placement: .navigation) {
Group {
if let icon = image {
icon
.resizable()
.frame(width: 25, height: 25)
} else {
Image(systemName: "app.dashed")
.resizable()
.frame(width: 25, height: 25)
}
if let image = cachedIconImage {
ToolbarItem(id: "ProgramViewIcon", placement: .navigation) {
image
.resizable()
.frame(width: 25, height: 25)
.padding(.trailing, 5)
}
} else {
ToolbarItem(id: "ProgramViewIcon", placement: .navigation) {
Image(systemName: "app.dashed")
.resizable()
.frame(width: 25, height: 25)
.padding(.trailing, 5)
}
.padding(.trailing, 5)
}
}
.navigationTitle(program.name)
.formStyle(.grouped)
.animation(.whiskyDefault, value: configSectionExpanded)
.animation(.whiskyDefault, value: envArgsSectionExpanded)
.task {
guard let peFile = program.peFile else { return }
let task = Task.detached {
guard let image = peFile.bestIcon() else { return nil as Image? }
return Image(nsImage: image)
}
self.image = await task.value
if let fetchedImage = program.peFile?.bestIcon() { self.cachedIconImage = Image(nsImage: fetchedImage) }
}
}
}
+17 -9
View File
@@ -177,16 +177,24 @@ struct WhiskyApp: App {
return
}
getconf.waitUntilExit()
let getconfOutput = pipe.fileHandleForReading.readDataToEndOfFile()
if let getconfOutputString = String(data: getconfOutput, encoding: .utf8) {
let d3dmPath = URL(fileURLWithPath: getconfOutputString.trimmingCharacters(in: .whitespacesAndNewlines))
.appending(path: "d3dm").path
do {
try FileManager.default.removeItem(atPath: d3dmPath)
} catch {
return
let getconfOutput = {() -> Data in
if #available(macOS 10.15, *) {
do {
return try pipe.fileHandleForReading.readToEnd() ?? Data()
} catch {
return Data()
}
} else {
return pipe.fileHandleForReading.readDataToEndOfFile()
}
}()
guard let getconfOutputString = String(data: getconfOutput, encoding: .utf8) else {return}
let d3dmPath = URL(fileURLWithPath: getconfOutputString.trimmingCharacters(in: .whitespacesAndNewlines))
.appending(path: "d3dm").path
do {
try FileManager.default.removeItem(atPath: d3dmPath)
} catch {
return
}
}
}
@@ -121,11 +121,11 @@ public extension Process {
extension FileHandle {
func nextLine() -> String? {
if let line = String(data: availableData, encoding: .utf8) {
guard !line.isEmpty else { return nil }
guard let line = String(data: availableData, encoding: .utf8) else { return nil }
if !line.isEmpty {
return line
} else {
return nil
}
return nil
}
}
+1 -1
View File
@@ -40,7 +40,7 @@ extension PEFile {
do {
try handle.seek(toOffset: UInt64(offset))
if let data = try handle.read(upToCount: 8) {
let string = String(data: data, encoding: .utf8) ?? ""
let string = String(data: data, encoding: .utf8) ?? String()
self.name = string.replacingOccurrences(of: "\0", with: "")
} else {
self.name = ""
+2 -2
View File
@@ -33,7 +33,7 @@ public class Tar {
try process.run()
if let output = try pipe.fileHandleForReading.readToEnd() {
let outputString = String(data: output, encoding: .utf8) ?? ""
let outputString = String(data: output, encoding: .utf8) ?? String()
process.waitUntilExit()
let status = process.terminationStatus
if status != 0 {
@@ -54,7 +54,7 @@ public class Tar {
try process.run()
if let output = try pipe.fileHandleForReading.readToEnd() {
let outputString = String(data: output, encoding: .utf8) ?? ""
let outputString = String(data: output, encoding: .utf8) ?? String()
process.waitUntilExit()
let status = process.terminationStatus
if status != 0 {