Swift thinks my string is an MDL material - swift3

I'm trying to save data in an image metadata in iOS/Swift3. It does not appear that CG will let you save out custom tags (is that true?) so I JSON encoded my dictionary and put the result as a string into the TIFF tag's ImageDescription. When I load the image and get the metadata back...
if let data = NSData(contentsOfFile:oneURL.path), let imgSrc = CGImageSourceCreateWithData(data, options as CFDictionary) {
let allmeta = CGImageSourceCopyPropertiesAtIndex(imgSrc, 0, options as CFDictionary) as? [String : AnyObject]
The allMeta contains (among other things):
▿ 0 : 2 elements
- key : ImageDescription
- value : {
"CameraOrientationW" : 0.1061191,
"CameraOrientationZ" : -0.01305595,
"CameraOrientationX" : 0.01319851,
"CameraOrientationY" : 0.9941801
}
Which has the JSON data, yay! So now I simply have to get the TIFF metadata, get the ImageDescription from that, and de-JSON it...
let tiffmeta = allmeta?["{TIFF}"]
if let tiffMeta = tiffmeta {
let descmeta = tiffMeta["ImageDescription"]
var descdata = descmeta?.data(usingEncoding: NSUTF8StringEncoding)!
let descdict = try? JSONSerialization.jsonObject(with: descdata, options: [])
But this will not compile. Xcode puts an error on the let descdata line:
Value of type 'MDLMaterialProperty??' has no member 'data'
I tried casting it to String on the line above, at which point it complains I didn't unwrap the optional MDLMaterialProperty.
Am I missing something obvious here?

So just to close this one, this appears to be a problem in the compiler. I made a number of minor changes to the syntax, nothing that had any actual effect on the code, and suddenly it decided the object was indeed a string.

Related

Can Apple Watch workout metadata use JSON strings?

I've got a working Apple Watch workout app. My metadata saves and all workout data flows to iPhone. I'm also able retrieve and display the data. But when i try to add arrays converted to ... json strings ... to metadata, the app crashes on save. Every time. I've tried numerous variations, always it's the same. Here's the latest code to crash... and it's perfectly fine.
GOOD CODE that works, but return string...
CRASHES with every HKWorkoutSession save.
func toJSON(array: [[String: Any]]) throws -> String {
let data = try JSONSerialization.data(withJSONObject: array, options: [])
return String(data: data, encoding: .utf8)!
}
NOW ... when my configuration class is converted with strings created using the function below, metadata saves just fine... and i'm back where i started. Wondering how to restore the [[String:Any]] arrays from String.
SAVES on WatchOS 3
This code creates a string from array of dictionaries.
What I'm needing help with is function to restore strings created using this function back into original form of [ [ String : Any ] ]
func joinedRepresentationOfArrayOfArrays(newArray: [[String : Any]]) -> String {
var newString = ""
for dictionary in newArray {
newString = newString.appending("[")
for (key, value) in dictionary {
newString = newString.appending("[\(key) : \(value), ")
}
newString = newString.appending("], ")
}
newString = newString.appending("], ")
return newString
}

Swift 2 to 3 conversion error with Value of type '[Any]' has no member

I'm having a problem with a Swift 2 to 3 conversion piece of work and some of the remains syntax giving: Value of type '[Any]' has no member errors.
I was hoping someone could point me at a good solution.
Swift 2 code
Swift 2 code
func search() {
epsonPrinters = [Printer]()
starPrinters = [Printer]()
epson_startSearching()
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) { [unowned self] in
let devices = SMPort.searchPrinter()
self.starPrinters = devices.map { portInfo -> Printer in
let p = Printer(
id: portInfo.modelName,
make: "Star Micronics",
model: portInfo.modelName,
portName: portInfo.portName)
if let name = portInfo.modelName as? String {
p.emulation = name.containsString("TSP143") ? "StarGraphics" : "StarLine"
}
return p
}
}
}
Swift 3 Code (I've added comments above areas with errors)
func search() {
epsonPrinters = [Printer]()
starPrinters = [Printer]()
epson_startSearching()
DispatchQueue.global(qos: .background).async { [unowned self] in
let devices = SMPort.searchPrinter()
self.starPrinters = [devices.map { portInfo -> Printer in
// id, model and portName in below fails with messages like:
// Value of type '[Any]' has no member 'modelName'
let p = Printer(
id: portInfo.modelName,
make: "Star Micronics",
model: portInfo.modelName,
portName: portInfo.portName)
// error on portInfo.modelName
// Value of type '[Any]' has no member 'modelName'
if let name = portInfo.modelName as? String {
p.emulation = name.containsString("TSP143") ? "StarGraphics" : "StarLine"
}
return p
}!]
}
}
I know that I can replace the 'id:...' part with the likes of:
id: ((portInfo[0] as AnyObject).modelName) ?? "",
But this isn't correct because PortInfo can have none, 1 or multiples depending on the number of printers we find.
I'd appreciate any suggestions for refactoring this in an elegant way that is good Swift 3 syntax and likely to survive into Swift 4.
I'm working in Xcode 8.3.2
When you get some errors about types, you'd better check what type each variable has. When you select devices in the line let devices = ..., Quick Help of Xcode will show you something like this:
Declaration let devices: [Any]?
First, it's an Optional and you need to unwrap it, before using the actual content.
Second, the type of the elements in devices is Any, to which you cannot apply any methods (including property accessors). You need to cast it to an appropriate type at an appropriate place.
To solve the two things above, you can write something like this:
guard let devices = SMPort.searchPrinter() as? [PortInfo] else {
fatalError("This may never happen")
}
With guard statement above, Quick Help will show you:
Declaration let devices: [PortInfo]
Non-Optional, simple Array of PortInfo, so you can use any methods of PortInfo for the elements of this devices.
I would translate your Swift 2 code into Swift 3 like this:
func search() {
epsonPrinters = []
starPrinters = []
epson_startSearching()
DispatchQueue.global(qos: .default).async {
guard let devices = SMPort.searchPrinter() as? [PortInfo] else {
fatalError("This may never happen")
}
self.starPrinters = devices.map { portInfo -> Printer in
let p = Printer(
id: portInfo.modelName,
make: "Star Micronics",
model: portInfo.modelName,
portName: portInfo.portName)
if let name = portInfo.modelName {
p.emulation = name.contains("TSP143") ? "StarGraphics" : "StarLine"
}
return p
}
}
}
You may need some fixes (not many, I believe) to use this code, as you are not showing all relevant things such as the definition of Printer.

cast from float to nsdecimalnumber always fails swift

I am reading my data through a web service that returns a percentage as a string and I need to format it properly in my app. Currently I receive an error message that reads "Argument labels '(_:)' do not match any available overloads".
Suggestions on how to resolve this issue?
if let dUnInsured = result[0]["UnInsured"] as? String, let doubleNum = Double(dUnInsured) {
let sUnInsured = dollarFormatter.string(from: (NSDecimalNumber(Decimal(doubleNum))))!
self.inUninsured.text = sUnInsured
}
Try like this way.
if let dUnInsured = result[0]["UnInsured"] as? String, let doubleNum = Double(dUnInsured) {
let sUnInsured = dollarFormatter.string(from: (NSNumber(value: doubleNum)))!
self.inUninsured.text = sUnInsured
}

Force NSTextField to Only Accept Decimal (#.#) Numbers and Periods

I'm trying to make it so an NSTextField will only accept numbers and periods like 12.4 and 3.6 in a Mac app.
I feel like I'm getting pretty close after reviewing other SO questions, but I can't quite get it. The below code works except that it won't allow . characters. It returns true and doesn't beep at me when I type a . but it won't let the character appear in the field.
class decimalFormatter: NumberFormatter {
override func isPartialStringValid(_ partialString: String, newEditingString newString: AutoreleasingUnsafeMutablePointer<NSString?>?, errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool {
//Allows the text to be deleted
if partialString.isEmpty {
return true
}
//Check for #.# numbers
let charSet = NSCharacterSet(charactersIn: "1234567890.").inverted
if partialString.rangeOfCharacter(from: charSet) != nil{
NSBeep()
return false
}else{
return true
}
}
}
Any idea what I'm doing wrong?
I found a simpler way to do it. Inside controlTextDidChange I just did this:
let charSet = NSCharacterSet(charactersIn: "1234567890.").inverted
let chars = fieldDuration.stringValue.components(separatedBy: charSet)
fieldDuration.stringValue = chars.joined()
It works great!
#Clifton Labrum solution is really great but it doesn't reduce the field to Decimal (#.#), you can stil put some inputs as 1.2.4 which would lead to an error when trying tu cast it to Float.
Here is a draft of an extension that worked fine for me ( In Swift 4 )
public override func controlTextDidChange(_ obj: Notification) {
if let textfield = obj.object as? NSTextField,
textfield == self.quantityTextField {
var stringValue = textfield.stringValue
// First step : Only '1234567890.' - #Clifton Labrum solution
let charSet = NSCharacterSet(charactersIn: "1234567890.").inverted
let chars = stringValue.components(separatedBy: charSet)
stringValue = chars.joined()
// Second step : only one '.'
let comma = NSCharacterSet(charactersIn: ".")
let chuncks = stringValue.components(separatedBy: comma as CharacterSet)
switch chuncks.count {
case 0:
stringValue = ""
case 1:
stringValue = "\(chuncks[0])"
default:
stringValue = "\(chuncks[0]).\(chuncks[1])"
}
// replace string
textfield.stringValue = stringValue
}
}
This prevent multiple occurences of . , even if I know that's not the best algorithmic way to do this. For instance 1.2.4 becomes 1.2 when pasted, and by keyboard you can't add another .

Storing NSArray in UIPasteboard

I have several text files which I want to transfer between 2 Apps. (ie. free and paid versions of the same App).
I'm using UIPasteboard to do this. The contents of the files are held in memory as NSArrays, and so I want to copy these NSArrays to the pasteboard (lite version), and read them from the pasteboard (full version).
For some reason the data cannot be read back from the pasteboard. The data is being returned as a NSData object, rather than NSArray, which I think means that it is not in the required format for the pasteboard type I am using, which is "public.utf8-plain-text".
When I read/write NSStrings with this pasteboard type, it works fine.
I searched through Apple docs, etc, to see if there is a different type I should be using for NSArrays, (or other property list objects), but drew a blank.
Writing to the pasteboard: (In the following pDataOutput is an array of strings, file contents) :
NSMutableArray *lArrayCopy = [gGlobalData.cPasteBoard.items mutableCopy];
[lArrayCopy replaceObjectAtIndex:pDataFileIdx
withObject:[NSDictionary dictionaryWithObject:pDataOutput
forKey:#"public.utf8-plain-text"]];
gGlobalData.cPasteBoard.items = lArrayCopy;
[lArrayCopy release];
Reading from the pasteboard:
NSArray *lPBItems = [pPasteBoard valuesForPasteboardType:#"public.utf8-plain-text"
inItemSet:nil];
NSLog(#"PB Items = NSArray of count %d", lPBItems.count);
The above returns:
PB Items = NSArray of count 0
As mentioned above, it returns the data correctly as NSStrings if written as NSStrings.
Any help would be very much appreciated.
Thanks
Stephen C
I ran into the same issue and I think the valueForPasteboardType family of methods are broken and always return NSData.
Here is my solution:
NSArray * lArrayFromPasteBoard = [pPasteBoard valueForPasteboardType:#"com.my.custom.type"];
if ([lArrayFromPasteBoard isKindOf:[NSData class]])
{
lArrayFromPasteBoard = [[NSPropertyListSerialization propertyListWithData:(NSData*)lArrayFromPasteBoard options:0 format:0 error:0];
}
hopefully this will make it so the code in the if won't get called anymore once apple fixes their bug
As of iOS 8.3, UIPasteboard still has this bug. I wrote an extension for UIPasteboard to handle this:
extension UIPasteboard {
func arrayForPasteboardType(pasteboardType: String) -> NSArray? {
switch valueForPasteboardType(pasteboardType) {
case let array as NSArray:
return array
case let data as NSData:
if let array = NSPropertyListSerialization.propertyListWithData(data, options: 0, format: nil, error: nil) as? NSArray {
return array
}
default:
break
}
return nil
}
}