I'd like to create a small helper app for new students do read/write User default settings.
Since it was not possible using the UserDefaults class I decided to use the "/usr/bin/defaults". Unfortuntely it seems not to return anything. Debug output shows "Got data: 0 bytes"
Here is a sample code:
import SwiftUI
func readDefaults(domain : String, key :String) -> String {
let cmdPath = "/usr/bin/defaults"
//let cmdPath = "/bin/ls"
let cmd = Process()
let pipe = Pipe()
cmd.standardOutput = pipe
cmd.standardError = pipe
cmd.executableURL = URL(fileURLWithPath: cmdPath, isDirectory: false, relativeTo: nil)
cmd.arguments = ["read", domain, key]
//cmd.arguments = ["/", "/Library"]
print("Shell command: \(cmdPath) \(cmd.arguments?.joined(separator: " ") ?? "")")
var d : Data?
do {
try cmd.run()
d = pipe.fileHandleForReading.readDataToEndOfFile()
cmd.waitUntilExit()
} catch let e as NSError {
return "ERROR \(e.code): \(e.localizedDescription)"
} catch {
return "ERROR: call failed!"
}
// get pipe output and write is to stdout
guard let d else {
return "ERROR: Can't get pipe output from command!"
}
print("Got data: \(d)")
if let s = String(data: d, encoding: String.Encoding.utf8) {
print("Got result: \(s)")
return s
} else {
return "ERROR: No output from pipe."
}
}
struct ContentView: View {
let foo = readDefaults(domain: "com.apple.Finder", key: "ShowHardDrivesOnDesktop")
var body: some View {
VStack {
Text("ShowHardDrivesOnDesktop: \(foo.description)")
}
.padding()
}
}
#Preview {
ContentView()
}
This code works well e.g. for "ls" when the comments are changed for cmdPath and cmd.arguments.
What do I miss in order to get it working with defaults?
Since it was not possible using the UserDefaults class
What’s not possible? Reading a user default from a specific domain, like com.apple.Finder?
You are correct that UserDefaults can’t do that, but running the defaults tool is not the best alternative. Rather, use CFPreferences. For example:
import Foundation
func main() {
guard
let obj = CFPreferencesCopyAppValue("ShowHardDrivesOnDesktop" as NSString, "com.apple.Finder" as NSString),
CFGetTypeID(obj) == CFBooleanGetTypeID(),
let showHardDrivesOnDesktop = obj as? Bool
else {
fatalError()
}
print(showHardDrivesOnDesktop)
}
main()
WARNING Unless otherwise documented, system preferences like this are an implementation detail. It might be OK to use them in a limited scope — like in a managed environment where you control all the copies of the code — but you should not rely on implementation details in a product that you deploy widely.
The code above won’t work if your app is sandboxed. Then again, defaults won’t work in that case either (-
Finally, while you shouldn’t need to use defaults in this case, if you find yourself in a situation where the only way to do something is by invoking a command-line tool, I strongly recommend that you use the Subprocess package rather than Process. Process is really hard to use correctly.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"