Barcode string value when using the Vision Framework of iOS11 - swift

The following piece of Swift code is using the new iOS11 Vision framework to analyze an image and find QR codes within it.
let barcodeRequest = VNDetectBarcodesRequest(completionHandler {(request, error) in
for result in request.results! {
if let barcode = result as? VNBarcodeObservation {
if let desc = barcode.barcodeDescriptor as? CIQRCodeDescriptor {
let content = String(data: desc.errorCorrectedPayload, encoding: .isoLatin1)
print(content) //Prints garbage
}
}
}
}
let image = //some image with QR code...
let handler = VNImageRequestHandler(cgImage: image, options: [.properties : ""])
try handler.perform([barcodeRequest])
However, the problem is that the desc.errorCorrectedPayload returns the raw encoded data as it has been read from the QR code.
In order to get a printable content string from the descriptor one must decode this raw data (e.g. determine the mode from the first 4 bits).
It gets even more interesting because Apple already has code for decoding raw data in the AVFoundation. The AVMetadataMachineReadableCodeObject class already has the .stringValue field which returns the decoded string.
Is it possible to access this decoding code and use it in Vision framework too?

It seems that now you can get a decoded string from a barcode using new payloadStringValue property of VNBarcodeObservation introduced in iOS 11 beta 5.
if let payload = barcodeObservation.payloadStringValue {
print("payload is \(payload)")
}

Related

How do I get a String from UIPasteboard.value(forPasteboardType: kUTTypePlainText)?

The documentation for UIPasteboard.value(forPasteboardType:) says, "For example, if the representation type is kUTTypePlainText … the method returns an NSString object".
However, on my phone, UIPasteboard.general.value(forPasteboardType: kUTTypePlainText as String) as? String is nil. It appears to return Data. So I made my code more elaborate:
if let text = value as? String {
return text
} else if let data = value as? Data {
return String(data: data, encoding: String.Encoding.utf8)
}
If I copy plain text from a text field in a Swift app, it falls into the second code path, and returns the text as expected. But if I, for example, open Twitter and copy the link to a tweet, it falls into the second code path and String.init(data:encoding:) returns nil.
Am I misusing UIPasteboard? I thought that if you asked for text, it would give you text, but it doesn't seem to be filtering at all if I'm getting something other than UTF-8 from Twitter.
The app is built with Xcode 12.5 running on iOS 14.4.2.

Issue with UserDefaults (converting data to array and back)

What I want to do:
I want to get an array from UserDefaults that I saved beforehand and append a custom object to it. Afterwards I want to encode it as a Data-type again and set this as the UserDefaults Key again.
My problem:
The encoding part is what is not working as intended for me.
It says: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000011a540
But I do not know how to fix this.
Below is my code for more context:
do {
let decoded = defaults.object(forKey: "ExArray") as! Data
var exo = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! [Exerc]
exo.append(datas[indexPath.row])
let enco = try NSKeyedArchiver.archivedData(withRootObject: exo, requiringSecureCoding: false) <- Here is the error
defaults.set(enco, forKey: "ExArray")
} catch {
print("Error encoding custom object NOSEARCHO")
}
This is how Exerc looks:
struct Exerc: Codable {
var title: String
var exID: String
}
Seems like you are not using the archiver features, so why don't you just use the codable?
do {
let key = "ExArray"
let decoded = defaults.data(forKey: key)!
var exo = try JSONDecoder().decode([Exerc].self, from: decoded)
exo.append(datas[indexPath.row])
let enco = try JSONEncoder().encode(exo)
defaults.set(enco, forKey: key)
} catch {
print("Error encoding/decoding custom object NOSEARCHO", error)
}
It just a simple refactored MVP of the original code, but you can even work a bit on this and make it human readable right in the plist file!

How do I convert a file into a JSON Object in SwiftyJson

I am trying to import Json data from a user uploaded txt file into a standard object that I can use via SwiftyJson framework
Here is the contents of the text fie:
{
"String": "answer",
"String2": "answer2"
}
I have successfully read it and turned it into a String file using:
let openPanel = NSOpenPanel()
let arrayOfExtensions = ["txt"]
openPanel.allowedFileTypes = arrayOfExtensions
let result = openPanel.runModal()
if result == NSFileHandlingPanelCancelButton {
return
}
let fileUrl = openPanel.URL
do {
let stringResult = try String(contentsOfURL: fileUrl!, encoding: NSUTF8StringEncoding)
print (stringResult)
completionhandler(retrievedData: stringResult, error: nil)
I am trying to convert this into a JSON object using:
let jsonFile = JSON(contentsOfFile)
The problem is that the resulting JSON object created appears to be blank for all the fields except rawvalue.
Here is the screenshot from the debug console.
How to I sucessfully read in the string from the file and then make it populate via SwiftJson correctly?
The problem above was that I was using the wrong method to parse it into JSON.
SwiftyJSON seems to be badly documented hence others had a similar problem.
The correct way to do this is to use :
let jsonFile = JSON.parse(string:contentsOfFile)

Dictionary from Plist gives inconsistent results with same key

Inspired by this question:
Is there a way of getting a Mac's icon given its model number?
I was working around getting similar results but using the Mac's model identifier as a start, not the Mac's model number.
But I'm stuck with a weird problem when using my Mac's model identifier to find the related system icon.
On my office machine I get "iMac14,2" as a model identifier.
When I load this plist as a dictionary...
/System/Library/PrivateFrameworks/ServerInformation.framework/Versions/A/Resources/English.lproj/SIMachineAttributes.plist
...I see that it has keys for all Mac models, including "iMac14,2", and the values contain, among other things, the URL for the icon.
However, when I try to grab this dictionary's value for the identifier key ("iMac14,2") I got nil, although I get the actual value if I grab it with a literal key.
But the literal key and the key I get from my modelIdentifier function are the same. It looks like it anyway...
To grab the model identifier:
func modelIdentifier() -> String? {
let service: io_service_t = IOServiceGetMatchingService(kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice").takeUnretainedValue())
let cfstr = "model" as CFString
if let model = IORegistryEntryCreateCFProperty(service, cfstr, kCFAllocatorDefault, 0).takeUnretainedValue() as? NSData {
if let nsstr = NSString(data: model, encoding: NSUTF8StringEncoding) {
return String(nsstr)
}
}
return nil
}
if let id = modelIdentifier() {
println(id) // prints "iMac14,2"
}
Finding the value with this result fails:
if let dict = NSDictionary(contentsOfFile: "/System/Library/PrivateFrameworks/ServerInformation.framework/Versions/A/Resources/English.lproj/SIMachineAttributes.plist") as? [String:AnyObject] {
if let id = modelIdentifier() {
if let result = dict[id] as? [String:AnyObject] {
print(result) // nil
}
}
}
But if I do the same with a literal string it works:
if let result = dict["iMac14,2"] as? [String:AnyObject] {
print(result)
}
result:
[architecture: x86_64, LOCALIZABLE: {
description = "iMac with 27\" widescreen LED-backlit display, introduced late 2013.";
marketingModel = "27\" iMac (Late 2013)";
model = iMac;
processor = "Quad-core Intel Core i5, Quad-core Intel Core i7";
}, hardwareImageName: /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/com.apple.imac-unibody-27-no-optical.icns]
What is wrong here?
The strings look the same but aren't the same?
Or am I missing something else?
The NSData created by IORegistryEntryCreateCFProperty for the model key contains a NUL-terminated string. You're including that NUL in your NSString.
Create your string this way instead:
if let nsstr = NSString(CString: UnsafePointer<Int8>(model.bytes), encoding: NSUTF8StringEncoding) {
return String(nsstr)
}

Content Blocker extension with a String instead of a file

I'm using the function NSItemProvider(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json") in a content blocker extension.
The thing is that all my rules are stored in a few dictionaries and, when I'm using this function, it's always because the rules have changed. I'm currently creating a String from these dictionaries that looks like "[{\"trigger\": {\"url-filter\": \"webiste.com\"},\"action\": {"\type\": \"css-display-none\",\"selector\":\".testContentBlocker\"}}]"and I have to transform it in a JSON file to finally be able to use it in the function written previously described.
Instead of having to put the String in a JSON file to be able to use it, could I do something simpler to use NSItemProvider()?
By loading the extension up in the debugger (and by using Hopper), you can see that NSItemProvider(contentsOfURL:) is simply registering to provide data from the file's contents with type public.json.
(lldb) po attachment
<NSItemProvider: 0x7fd4c250f2a0> {types = (
"public.file-url",
"public.json"
)}
It's roughly equivalent to this:
// possible implementation of NSItemProvider.init(contentsOfURL:)
convenience init?(contentsOfURL fileURL: NSURL!)
{
self.init(item: fileURL, typeIdentifier: (fileURL.fileURL ? kUTTypeFileURL : kUTTypeURL) as String)
let type = UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, fileURL.pathExtension!, nil)?.takeRetainedValue() as! String
registerItemForTypeIdentifier(type) { completionHandler, expectedValueClass, options in
let data = try! NSData(contentsOfURL: fileURL, options: .DataReadingMappedAlways)
completionHandler(data, nil)
}
}
So you can do this yourself, in memory:
// get the data
let data = NSData(contentsOfURL: NSBundle.mainBundle().URLForResource("blockerList", withExtension: "json")!)
// put the data in an item provider
let attachment = NSItemProvider(item: data, typeIdentifier: kUTTypeJSON as String)
// send the item to Safari
let item = NSExtensionItem()
item.attachments = [attachment]
context.completeRequestReturningItems([item], completionHandler: nil);
If you want to provide content dynamically, you can use NSJSONSerialization to transform a dictionary into NSData at runtime.