How to pass optional<vector<optional<uuid>>> from C++ to Swift? - c++

I have following problem:
I have iOS application with C++ core. Data is bridged through C++<->ObjCpp<->ObjC chain, data structures are later imported in swift from ObjC.
I have optional<vector<optional>> in one of my structures and there is problem with importing it. Vector is converted to NSList, which is populated by data. Because there can't be nil in NSList, I add NSNull to it. If vector isn't optional - I can work with it in Swift, though it isn't very convenient.
let arr = struct.myArray as NSArray
var swift_arr: [UUID?] = []
for val in arr {
if val is NSNull {
swift_arr.append( nil )
} else if val is NSUUID {
swift_arr.append( val as? UUID )
} else {
fatalError()
}
}
But if array is optional - there is big problem, because when I try to bind optional value, Swift tries to convert it to [UUID?] from NSArray, and when there is NSNull among NSUUIDs, it crashes, because NSNull cannot be converted to UUID.
Is there any proper way to handle such arrays?

Related

How do I save the results of a MKLocalSearch to an Array?

I am experiencing a bit of trouble, while working on my app in SwiftUI.
I want to append relevant data, summarized in an object, to an array and return this.
While returning the array, I could see by debugging, that it is empty. Debugging in the for loop showed me, that location objects are created and appended, but are not being "saved" in the array. The "mapItems" array on the other hand has lots of members.
What am I missing?
Here is the method I came up with:
func searchForLocation(searchTerm: String) -> Array<Location>{
var locations = [Location]
let searchReqeust = MKLocalSearch.Request()
searchRequest.region = region //region is a published variable and is determined before
searchRequest.naturalLanguageQuery = searchTerm
let search = MKLocalSearch(request: searchRequest)
search.start{response, error in
//error handling
.
.
.
//empty response.mapItems handling
.
.
.
for item in response!mapItems{
let location = createLocationFromItem(item: item)
locations.append(location)
}
}
return locations
}
My locations class if following:
class Location: Identifiable{
var id= UUID()
var coordinates: CLLocationCoordinate2d
//And its proper init method
}
Your searchForLocation has an asynchronous function inside (search.start{...}),
and your code returns before it has finished getting the results.
To "wait" for the results use a completion/closure handler,
something like this:
func searchForLocation(searchTerm: String, completion: #escaping ([Location]) -> ()) {
var locations = [Location]() // <-- here
// ....
search.start{response, error in // <-- here asynchronous function
//... todo deal with errors, eg return completion([])
for item in response!mapItems {
let location = createLocationFromItem(item: item)
locations.append(location)
}
completion(locations) // <- here return when finished
}
}
and call the function like this:
searchForLocation(searchTerm: "Tokyo") { results in
print("\(results)") // <-- here results available, not before
}
I suggest you read-up on how to create and use asynchronous functions, these are important concepts to master to code effectively in Swift.

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 3: how to supply a C-style 'void *' object to iOS frameworks for use with callbacks? [duplicate]

This question already has answers here:
How to cast self to UnsafeMutablePointer<Void> type in swift
(4 answers)
Closed 5 years ago.
Foundation is chock full of functions that take an opaque void *info then later vend it back. In pre-ARC Objective C days, you could retain an object, supply it, then when it was handed back to your callback release it.
For example,
CGDataProviderRef CGDataProviderCreateWithData(void *info, const void *data, size_t size, CGDataProviderReleaseDataCallback releaseData);
typedef void (*CGDataProviderReleaseDataCallback)(void *info, const void *data, size_t size);
In this case, you could supply a retained object in info, then release it in the callback (after appropriate casting).
How would I do this in Swift?
With assistance from Quinn 'The Eskimo' at Apple I found out how to do this. Given an object:
let pixelBuffer: CVPixelBuffer
get a pointer:
Get an unmanaged object after retaining it:
let fooU: Unmanaged = Unmanaged.passRetained(pixelBuffer)
Convert it to a raw pointer
let foo: UnsafeMutableRawPointer = fooU.toOpaque()
Recover the object while releasing it:
Convert the raw pointer to an unmanaged typed object
let ptr: Unmanaged<CVPixelBuffer> = Unmanaged.fromOpaque(pixelPtr)
Recover the actual object while releasing it
let pixelBuffer: CVPixelBuffer = ptr.takeRetainedValue()
The following code has been tested in an app. Note without Apple's help I'd never have figured this out thus the Q & A! Hope it helps someone!
Also, note the use of #convention(c), something I'd never seen before!
let fooU: Unmanaged = Unmanaged.passRetained(pixelBuffer)
let foo: UnsafeMutableRawPointer = fooU.toOpaque()
/* Either "bar" works */
/* let bar: #convention(c) (UnsafeMutableRawPointer?, UnsafeRawPointer, Int) -> Swift.Void = { */
let bar: CGDataProviderReleaseDataCallback = {
(_ pixelPtr: UnsafeMutableRawPointer?, _ data: UnsafeRawPointer, _ size: Int) in
if let pixelPtr = pixelPtr {
let ptr: Unmanaged<CVPixelBuffer> = Unmanaged.fromOpaque(pixelPtr)
let pixelBuffer: CVPixelBuffer = ptr.takeRetainedValue()
CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
DispatchQueue.main.async {
print("UNLOCKED IT!")
}
}
}
let val: CVReturn = CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0))
if val == kCVReturnSuccess,
let sourceBaseAddr = CVPixelBufferGetBaseAddress(pixelBuffer),
let provider = CGDataProvider(dataInfo: foo, data: sourceBaseAddr, size: sourceRowBytes * height, releaseData: bar)
{
let colorspace = CGColorSpaceCreateDeviceRGB()
let image = CGImage(width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: sourceRowBytes,
space: colorspace, bitmapInfo: bitmapInfo, provider: provider, decode: nil,
shouldInterpolate: true, intent: CGColorRenderingIntent.defaultIntent)
/* CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) */
return image
} else {
return nil
}
Quinn recently updated the Apple Forum thread on this, stating that somehow this technique never made it into either of the two Apple Swift Documents, and that he just entered a rdar to get it added. So you won't find this info anywhere else (well, at least now!)

Convert NSUUID to UnsafePointer<UInt8>

Following the update to Swift 3, it appears both getUUIDBytes and getBytes are not available on the UUID object.
let uuid = UIDevice.current.identifierForVendor
let mutableUUIDData = NSMutableData(length:16)
uuid.getBytes(UnsafeMutablePointer(mutableUUIDData!.mutableBytes))
// ^^^ compiler error, value of type UUID? has no member getBytes
I get this error even when getBytes is listed as a method on UUID in the documentation: https://developer.apple.com/reference/foundation/nsuuid/1411420-getbytes
One right way:
let uuid = UIDevice.current.identifierForVendor!
var rawUuid = uuid.uuid
withUnsafePointer(to: &rawUuid) {rawUuidPtr in //<- `rawUuidPtr` is of type `UnsafePointer<uuid_t>`.
rawUuidPtr.withMemoryRebound(to: UInt8.self, capacity: MemoryLayout<uuid_t>.size) {bytes in
//Use `bytes` only in this closure. (Do NEVER export `bytes` out of the closure.)
print(bytes[0],bytes[1])
//...
}
}
Another right way:
withUnsafePointer(to: &rawUuid) {rawUuidPtr in //<- `rawUuidPtr` is of type `UnsafePointer<uuid_t>`.
let bytes = UnsafeRawPointer(rawUuidPtr).assumingMemoryBound(to: UInt8.self)
//Use `bytes` only in this closure. (Do NEVER export `bytes` out of the closure.)
print(bytes[0],bytes[1])
//...
}
As already commented by Rob, exporting the pointer passed to the closure argument of withUnsafeBytes is completely NOT guaranteed. A slight change of the context (32-bit/64-bit, x86/ARM, Debug/Release, adding seemingly unrelated code...) would make your app a crasher.
And one more important thing is that UTF-8 Data of the uuidString and the byte sequence of NSUUID.getBytes are completely different:
let nsUuid = uuid as NSUUID //<-Using the same `UUID`
let mutableUUIDData = NSMutableData(length:16)!
nsUuid.getBytes(mutableUUIDData.mutableBytes.assumingMemoryBound(to: UInt8.self))
print(mutableUUIDData) //-><1682ed24 09224178 a279b44b 5a4944f4>
let uuidData = uuid.uuidString.data(using: .utf8)!
print(uuidData as NSData) //-><31363832 45443234 2d303932 322d3431 37382d41 3237392d 42343442 35413439 34344634>
You are thinking too complicated:
func getUUID ( ) -> Data {
let uuid = NSUUID()
var bytes = [UInt8](repeating: 0, count: 16)
uuid.getBytes(&bytes)
return Data(bytes: bytes)
}
Why does that work?
Consider you have:
func printInt(atAddress p: UnsafeMutablePointer<Int>) {
print(p.pointee)
}
then you can in fact do this:
var value: Int = 23
printInt(atAddress: &value)
// Prints "23"
but you can also do this:
var numbers = [5, 10, 15, 20]
printInt(atAddress: &numbers)
// Prints "5"
It's a form of "implicit bridging". To quote from Swiftdoc.org:
A mutable pointer to the elements of an array is implicitly created
when you pass the array using inout syntax.
This implicit bridging only guarantees valid pointers until the current function returns. Such pointers must never "escape" the current function context, but using them as an inout argument is always safe, as inout arguments were always only guarantee to be valid until the called function returns and the called function must return prior to the current one, so this cannot go wrong.
And for those that don't know, casting UUID to NSUUID (... as NSUUID) and the other way round (... as UUID) is guaranteed to always succeed. But if you insist on using UUID, the easiest way is:
private
func getUUID ( ) -> Data {
var uuid = UUID().uuid
return withUnsafePointer(to: &uuid) {
return Data(bytes: $0, count: MemoryLayout.size(ofValue: uuid))
}
}

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
}
}