CGImage/ImageIO Code works in playground but not in OSX app - swift

This routine returns nil in OSX 10.13.2 Beta but not in a playground. Don't have an older OS to test with at the moment.
func getImage(_ url: CFURL) -> CGImage? {
let sourceOptions = [
kCGImageSourceShouldAllowFloat as String: kCFBooleanTrue as NSNumber
] as CFDictionary
guard let imageSource = CGImageSourceCreateWithURL(url, sourceOptions) else { return nil }
guard let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
let imageSourceStatus = CGImageSourceGetStatus(imageSource)
Swift.print("image nil, Image Source Status = \(imageSourceStatus)")
return nil
}
return image
}
imageSource is non-nil, but image is nil. The console message is "Image Source Status = CGImageSourceStatus", which is not one of the valid enum values.
Tried with both Swift 3.2 and Swift 4.0. The Dictionary arg in CGImageSourceCreateWithURL can be set to nil; nothing changes.
The correct frameworks are in the project (although it linked without them, so I'm not sure they matter.) Did "import ImageIO" but it built without it, so again I'm not sure in matters.
The URL is absolutely a valid file URL -- like I said, this works in the playground, and I've tried a number of different files and file types (tiff, jpg and png).
Any ideas?

Related

Memory leak creating a CVPixelBuffer in Swift using VTPixelTransferSessionTransferImage

I am using VTPixelTransferSessionTransferImage to modify the size and pixel format of a CVPixelBuffer. I am struggling to get to the bottom of a memory leak using this code block.
I have found several similar issues but all of the solutions are ObjC, and do not apply in Swift because of the memory management differences. Any help would be much appreciated.
I should note, when this method is called and the size/format already match (when coming from AVFoundation) I do not see a leak, but when CVPixelBuffer comes from a Blackmagic source the leak occurs. Simply returning the sampleBuffer as on iOS results in no leak on macOS with either AVFoundation or Blackmagic sources.
private func convertPixelBuffer(_ sourceBuffer: CVPixelBuffer) -> CVPixelBuffer? {
#if os(macOS)
guard let session = pixelTransferSession else { return nil }
let state = self.state
var destinationBuffer: CVPixelBuffer? = nil
let status: CVReturn = CVPixelBufferCreate(kCFAllocatorDefault, state.width, state.height, kCVPixelFormatType_420YpCbCr8BiPlanarFullRange, pixelBufferAttributes as CFDictionary?, &destinationBuffer)
guard status == 0, let destinationBuffer = destinationBuffer else { return nil }
// transfer the image
VTPixelTransferSessionTransferImage(session, from: sourceBuffer, to: destinationBuffer)
return destinationBuffer
#else
return sourceBuffer
#endif
}
Any suggestions would be much appreciated.

optional convenience init with multiple attempts to initialise

I have a factory method on UIImage that looks in multiple bundles for the image as the projects I'm working on are sometimes broken up.
Key is an enum for type safety around various properties.
public extension UIImage {
static func with(_ key: Key, in bundle: Bundle? = nil) -> UIImage? {
if let image = UIImage(key, in: bundle) { return image }
if let image = UIImage(key, in: .main) { return image }
if let image = UIImage(key, in: Bundle(for: BrandManager.self)) { return image }
print("Failed to find \(key.rawValue) in any bundle")
return nil
}
convenience init?(_ key: Key, in bundle: Bundle? = nil) {
self.init(named: key.rawValue, in: bundle, compatibleWith: nil)
}
}
This is fine, but obviously it means I need to add .with every time i.e. UIImage.with(.key) when I'd prefer to use the convenience init directly to drop with
Looking here Swift unwrap optional init inside convenience init
this isn't possible with convenience init... but is there another a way to get the syntax and look in multiple bundles? I can bury it inside UIImageView but I'm looking for it directly on UIImage
Thanks
As per the answer by #JeremyP
I tried url(forResource... but for some reason it always came back as nil... It was a good idea though...
Here's what I have, it feels pretty ugly to build the image twice.. but as I said the other one always came back with nil for the url. I tried adding the extension but it didn't help.
convenience init?(_ key: Key, in bundle: Bundle? = nil) {
var correctBundle: Bundle?
let bundles = [bundle,
.main,
Bundle(for: BrandManager.self)].compactMap { $0 }
for b in bundles {
guard UIImage(named: key.rawValue, in: b, compatibleWith: nil) != nil else { continue }
// guard b.url(forResource: key.rawValue, withExtension: nil) != nil else { continue }
correctBundle = b
break
}
self.init(named: name, in: correctBundle, compatibleWith: nil)
}
If you would accept UIImage[.key] as a plausible calling syntax, you could express your factory method as a static subscript.
Having said in the comments that I see nothing wrong with the with function, you can dispense with it.
Instead of trying to initialise the image directly, you can first search for it in all the bundles by looking for the path name or URL using e.g. Bundle.url(forResource:withExtension:) and then load the image from the first bundle where that returns non nil (or even use the URL to initialise the image).

URL keeps returning nil [duplicate]

I'm trying to create an URL variable from a string value. I don't understand why the resulting URL is nil
I have set up a new Xcode macOS project, placed a simple button on the View, created an action for that button and implemented the following code. The resulting url is nil.
I tried the same in Swift playground and there it worked...
#IBAction func buttonClicked(_ sender: Any) {
let urlAsString = "http://www.google.de/"
let url = URL(string: urlAsString)
if url != nil {
// Do work...
}
}
urlAsString is "http://www.google.de/" but
url is nil
debugger
You've found a bug in the debugger!
[This bug is slated to be fixed in Xcode 12.5.]
It's easy to reproduce it:
We have paused at a breakpoint inside the condition. So obviously url is not nil or we wouldn't be here at all.
Another way to prove this is to po url in the console (see right-bottom of this screen shot):
Nevertheless, url shows as nil both in the tooltip and in the variables list. So the debugger is just lying to you: url is not nil. Don't worry, be happy. Your code is working fine.
EDIT The bug has something to do with the Swift Foundation overlay. If you change the declaration of url to this:
let url = NSURL(string: urlAsString)
...then everything works as expected.
And see also https://stackoverflow.com/a/58156592/341994

Convert String to URL (Why is resulting variable nil)

I'm trying to create an URL variable from a string value. I don't understand why the resulting URL is nil
I have set up a new Xcode macOS project, placed a simple button on the View, created an action for that button and implemented the following code. The resulting url is nil.
I tried the same in Swift playground and there it worked...
#IBAction func buttonClicked(_ sender: Any) {
let urlAsString = "http://www.google.de/"
let url = URL(string: urlAsString)
if url != nil {
// Do work...
}
}
urlAsString is "http://www.google.de/" but
url is nil
debugger
You've found a bug in the debugger!
[This bug is slated to be fixed in Xcode 12.5.]
It's easy to reproduce it:
We have paused at a breakpoint inside the condition. So obviously url is not nil or we wouldn't be here at all.
Another way to prove this is to po url in the console (see right-bottom of this screen shot):
Nevertheless, url shows as nil both in the tooltip and in the variables list. So the debugger is just lying to you: url is not nil. Don't worry, be happy. Your code is working fine.
EDIT The bug has something to do with the Swift Foundation overlay. If you change the declaration of url to this:
let url = NSURL(string: urlAsString)
...then everything works as expected.
And see also https://stackoverflow.com/a/58156592/341994

Implementing "Open file with" in Swift Cocoa App

I'm working on a macOS cocoa-app in Swift where I import several different file types into the app for the user to interact with.
I'm currently trying to determine if it's possible to implement the "Open file with" feature, so that the user could open those files in a different program if they wanted to:
I've found a few different SO questions that seem tangentially related to what I'm trying to do:
Swift: How to open file with associated application?
Launch OSX Finder window with specific files selected
...but so far nothing to indicate if it's possible to implement right-click Finder/file (?) access in the way I had in mind.
Apologies if this is too vague of a question; any help / guidance appreciated!
Without going into details, it's pretty straight forward:
Get the list of all known applications that can open a specific file type (see LSCopyApplicationURLsForURL, a Core Foundation C function).
Build the menu. You can use NSWorkspace (and probably URL) to get the application icons.
Use NSWorkspace.openFile(_:withApplication:) to tell the application to open the given document.
2022, Swift 5
Get app list associated with local file:
func getAppsAssociatedWith(_ url: URL?) {
guard let url = localFileURL,
let retainedArr = LSCopyApplicationURLsForURL( url as CFURL, .all)?.takeRetainedValue(),
let listOfRelatedApps = retainedArr as? Array<URL>
else {
return []
}
return listOfRelatedApps
}
Getting thumbnail for app:
let singleAppIcon = NSWorkspace.shared
.icon(forFile: appUrl.path)
.scaledCopy(sizeOfLargerSide: 17)
Open url with app:
#available(macOS 10.15, iOS 9.0, *)
public class func openUrlWithApp(_ urls: [URL], appUrl: URL) {
NSWorkspace.shared.open(urls, withApplicationAt: appUrl, configuration: NSWorkspace.OpenConfiguration())
}
In my app I'm cashing all apps icons in dictionary.
[someFile localURL : app icon]
If I have already got icon earlier - no need to get it once more
var relatedAppsThumbnails: [URL: Image] = [:]
func updateRelatedApps() {
guard let url = currImgUrl, // file url to get icons from related apps
let retainedArr = LSCopyApplicationURLsForURL( url as CFURL, .all)?.takeRetainedValue(),
let listOfRelatedApps = retainedArr as? Array<URL>
else {
relatedApps = []
return
}
self.relatedApps = listOfRelatedApps
// add app icon in case of it wasn't added yet
for appUrl in listOfRelatedApps {
if relatedAppsThumbnails[appUrl] == nil {
let nsImg = NSWorkspace.shared.icon(forFile: appUrl.path)
.scaledCopy(sizeOfLargerSide: 17)
relatedAppsThumbnails[appUrl] = Image(nsImage: nsImg)
}
}
}
LSCopyApplicationURLsForURL is deprecated. You can use this alternative:
func getListOfExternalApps(forURL url: URL) -> [(URL, Image)] {
let listOfExternalApps = NSWorkspace.shared.urlsForApplications(toOpen: url)
let icons = listOfExternalApps.map {
let nsimage = NSWorkspace.shared.icon(forFile: $0.path())
nsimage.size = CGSize(width: .s16, height: .s16)
return Image(nsImage: nsimage)
}
return Array(zip(listOfExternalApps, icons))
}