How to get macOS to present a dropped .stl file as public.standard-tesselated-geometry-format? - swift

I'm trying to add some drag-and-drop support to my SwiftUI macOS app to allow the user to drop .stl files onto the window. I’ve got the drop working, but I only seem to be able to validate against public.file-url, rather than the specific public.standard-tesselated-geometry-format (provided by ModelIO as kUTTypeStereolithography).
In my code, I do something like this:
func
validateDrop(info inInfo: DropInfo)
-> Bool
{
debugLog("STL: \(kUTTypeStereolithography)")
let has = inInfo.hasItemsConforming(to: [kUTTypeFileURL as String, kUTTypeData as String])
return has
}
func
performDrop(info inInfo: DropInfo)
-> Bool
{
inInfo.itemProviders(for: [kUTTypeFileURL as String, kUTTypeData as String, kUTTypeStereolithography]).forEach
{ inItem in
debugLog("UTIs: \(inItem.registeredTypeIdentifiers)")
inItem.loadItem(forTypeIdentifier: kUTTypeFileURL as String, options: nil)
{ (inURLData, inError) in
if let error = inError
{
debugLog("Error: \(error)")
return
}
if let urlData = inURLData as? Data,
let url = URL(dataRepresentation: urlData, relativeTo: nil)
{
debugLog("Dropped '\(url.path)'")
}
}
}
return true
}
This works well enough if I look for kUTTypeFileURL, but not if I look for kUTTypeStereolithography when I drop a .stl file onto the view.
I tried declaring it as an imported type but a) that didn’t seem to work and b) shouldn’t be necessary, since the existence of a symbolic constant implies macOS know about the type. I also don’t really know what it conforms to, although I tried both public.data and public.file-url.
I’d really like to be able to validate the type so that the user isn’t mislead by the drop UI. Currently, it indicates that my view will accept the drop if it’s not an .stl file, which I don’t want it to do.

Related

Content Previewer not loading on Xcode 12.5.1

I was working on a project the other day, and my preview stopped working. I got the error "Compiling Failed, cannot find "DataType" in scope" (all code/relationships will be shown below). I have restarted XCode a few times, and even commented out that area of code but it continues to not work, is anyone else having this problem and how can I fix it?
I am previewing a sheet called "Profile Sheet", just a standard swiftUI View, so the preview code looks like:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ProfileSheet()
}
}
In the profile sheet, it is accessing data from a static function associated with my model file (different struct, different file), the functions it is using, and the ones that define "DataType", the problematic generic are here:
static func saveData<DataType>(data: DataType, for key: String) {
defaults.setValue(data, forKey: key)
}
static func retrieveData<DataType>(defaultValue: DataType, for key: String) -> DataType {
guard let value = defaults.value(forKey: key) as? DataType else {return defaultValue}
return value
}
static func saveComplexData<DataType>(data: DataType, for key: String) where DataType: Codable {
let encodedData = try! JSONEncoder().encode(data)
defaults.setValue(encodedData, forKey: key)
}
static func retrieveComplexData<DataType>(defaultValue: DataType, for key: String) -> DataType where DataType: Codable {
guard let retrievedData = defaults.value(forKey: key) as? Data else { return defaultValue}
let decodedData = try! JSONDecoder().decode(DataType.self, from: retrievedData)
return decodedData
}
all of these function as intended, and obviously compile. Even when I run to launch the previewer and compiles my app does it builder properly, its only when it is then trying to launch the preview simulator.
Finally, I thought Id remove those functions entirely and just try to display a View that has no connection to them, so I previewed Text("test") and got the same error.
on Xcode 13 beta 3 & 4 I am able to run the simulator, but I cannot work only on those betas as they are still fairly unstable.
Any help is appreciated :)
I've seen quite a bit of weird behavior with frameworks, I think due to changes to the simulators to support Apple silicon. My temporary workaround is, in my app/extension targets, to add "arm64" to the Excluded Architectures build setting when building for the simulator (as your preview appears to be trying to do), and setting "Build Active Architecture Only" to No for all schemes. Might be worth a try.
As per triplette in apple developer answers.
Try to check it out
https://developer.apple.com/forums/thread/657913
I believe it's not an issue with your code it's an issue with Xcode due to apple silicon macs. try to do a software update and reinstall the Xcode.

Is there a way in Swift to partially match a generic?

That is, if I have a class C that takes two generics A and B, is there a way where I can cast an object to C where I don't care what B is?
My specific use case is that I need to bridge between NSView functionality and the new SwiftUI in a multi-window, but non-document based application. The problem I am having is, given an NSView, I need to obtain the SwiftUI View that it is managing (in my case a View called ContentView).
Note that I do have a solution, which I include below, but it involves the use of Mirror based reflection and I am wondering if there is a better way, most likely involving the use of as? to cast to a partial match of a generic.
The bridging is done using the NSHostingView hence it should seem that one would just do the following:
if let hostingView = NSApplication.shared.keyWindow?.contentView as? NSHostingView<ContentView> {
// do what I need with 'hostingView.rootView'
}
Unfortunately, NSHostingView.rootView does not return the actual ContentView that I created, it returns a modified version of that view dependant on the modifiers used. (In my case I'm using .environmentObject modifier.) As a result the if statement above never returns true because the type is not NSHostingView<ContentView> but rather NSHostingView<ModifiedContent<ContentView, _bunch_Of_Gobbletygook_Representing_The_Modifiers>>. One way to "solve" the problem is to print out the result of type(of: hostingView) when I create the window, and then change my cast to include the current version of the "gobbledygook", but that is brittle for the following two reasons:
If I change the modifiers, the compiler will not warn me that I need to update the cast, and
Since the "gobbledygook" contains single underscored values, I must assume those are internal details that could change. Hence without my changing any code, an OS update could cause the cast to start failing.
So I have created a solution in the form of the following NSView extension:
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
let mirror = Mirror(reflecting: self)
if let rootView = mirror.descendant("_rootView") {
let mirror2 = Mirror(reflecting: rootView)
if let content = mirror2.descendant("content") as? RootView {
return content
}
}
return nil
}
}
This allows me to handle my needs using the following:
private func currentContentView() -> ContentView? {
return NSApplication.shared.keyWindow?.contentView?.originalRootView()
}
... sometime later ...
if let contentView = currentContentView() {
// do what I need with contentView
}
What I would like to know is if there is a way to implement originalRootView without the use of reflection, presumably by allowing a partially specified cast to the ModifiedContent object. For example, something like the following (which does not compile):
extension NSView {
func originalRootView<RootView: View>() -> RootView? {
if let hostingView = self as? NSHostingView<RootView> {
return hostingView.rootView
}
if let hostingView = self as? NSHostingView<ModifiedContent<RootView, ANY>> {
return hostingView.rootView.content
}
return nil
}
}
The problem is what to put for "ANY". I would think some form of Any or AnyObject, but the complier complains about that. Essentially I would like to tell the compiler that I don't care what ANY is so long as the ModifiedContent has RootView as its content type.
Any ideas would be appreciated.
Just to make the results official, the answer is "no" there is no way to partially match a generic as of Swift 5.1.

filePromiseProvider writePromiseTo URL not working when dragging image to another application

I have File Promises implemented in a Cocoa app that allows dragging images from a view and dropping them to folders on the machine or to apps like preview or evernote. In the past this has worked well using the NSDraggingSource delegate and 'namesOfPromisedFilesDropped' method. This method would return the drop location and would allow me to write the image data directly there. So when dropping to an application icon like preview, or within an app like evernote, the file would be written and the app would either load or the image would simply show within.
Unfortunately this method was deprecated in 10.13 so it no longer gets called in new OS versions. Instead I've switched over to using the filePromiseProvider writePromiseTo url method of the "NSFilePromiseProviderDelegate" delegate. This method gets called, and the image data is processed. I get the destination URL and attempt to write the image data to this location. This works perfectly fine when simply dragging to folders on the Mac. But, when dragging to other app icons like Preview.app, or directly to a folder in Evernote, I get an error 'Error Domain=NSPOSIXErrorDomain Code=2 "No such file or directory"'.
I've attempted this using full URLs, and, URL paths. Regardless of either, when dragging to other apps it simply will not allow the drop or creation of the file at that location.
Is there some entitlement that may be missing? I've even attempted the Apple source code found here with the same error: File Promises Source
Here is the code I'm using now that's returning the error with inability to write to outside app locations. This only works for dragging to folders on the computer.
extension DragDropContainerView: NSFilePromiseProviderDelegate {
internal func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
let fileName = NameFormatter.getFormattedNameFromDate() + "." + fileType.getFileType().typeIdentifier
return fileName
}
internal func operationQueue(for filePromiseProvider: NSFilePromiseProvider) -> OperationQueue {
return workQueue
}
internal func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: #escaping (Error?) -> Void) {
if let snapshot = filePromiseProvider.userInfo as? SnapshotItem {
if let data = snapshot.representationData {
do {
try data.write(to: url)
completionHandler(nil)
} catch {
completionHandler(error)
}
}
}
}
}
Any help on this issue would be great. Ultimately, I simply want to be able to drag the image to an app and have that app accept the drag. This used to work but no longer does.
Update 1:
After extensive experimentation I've managed to find a 'solution' that works. I don't see why this works but some how this ultimately kicks off a flow that fires the old 'deprecated' method.
I create the dragged item;
let draggingItem = NSDraggingItem(pasteboardWriter: pasteboardWriter(forImageCanvas: self))
draggingItem.setDraggingFrame(screenshotImageView.frame, contents: draggingImage)
beginDraggingSession(with: [draggingItem], event: event, source: self)
This calls the below method;
private func pasteboardWriter(forImageCanvas imageCanvas: DragDropContainerView) -> NSPasteboardWriting {
let provider = FilePromiseProvider(fileType: kUTTypeJPEG as String, delegate: self)
provider.userInfo = imageCanvas.snapshotItem
return provider
}
This sets up the custom file promise session using the subclass below. As you can see, I'm not handling any logic here and it seems to be doing very little or nothing. As an added test to verify it's not really doing much, I'm setting the pasteboard type to 'audio'. I'm dragging an image not audio but it still works.
public class FilePromiseProvider : NSFilePromiseProvider {
public override func writableTypes(for pasteboard: NSPasteboard)
-> [NSPasteboard.PasteboardType] {
return [kUTTypeAudio as NSPasteboard.PasteboardType]
}
public override func writingOptions(forType type: NSPasteboard.PasteboardType,
pasteboard: NSPasteboard)
-> NSPasteboard.WritingOptions {
return super.writingOptions(forType: type, pasteboard: pasteboard)
}
}
So long at the above methods are implemented it apparently kicks off a flow that ultimately calls the 'deprecated' method below. This method works every time perfectly in Mojave showing that there must be an issue with the NSFilePromises API. If this method works, the file promises API should work the same but it does not;
override func namesOfPromisedFilesDropped(atDestination dropDestination: URL) -> [String]?
Once this method gets called everything works perfectly in Mojave for drags to app icons in the dock and directly into applications like Evernote returning the app to 100% drag drop functionality as it used to work in previous OS versions!
My file promises delegate is still in place but looks like the below. As you can see it's not doing anything any longer. However it's still required.
extension DragDropContainerView: NSFilePromiseProviderDelegate {
func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, fileNameForType fileType: String) -> String {
return ""
}
func filePromiseProvider(_ filePromiseProvider: NSFilePromiseProvider, writePromiseTo url: URL, completionHandler: #escaping (Error?) -> Void) {
}
}
Any comments on this 'solution' would be great. Any suggestions on how to do this better would also be welcomed. Please note, that according to Apple, there "is no guarantee that the file will be written in time" using the File Promises API. But with the above, the old deprecated method is somehow called in Mojave and works flawlessly every time.
Thanks

How to generalize form inputs from properties using functional reactiveSwift?

When doing forms with fields i want to send if there is a change i often do
let initialOrChangedName = Signal.merge(
nameChanged.signal,
self.viewDidLoadProperty.signal
.map { _ in nil }
)
where
private let nameChangedProperty = MutableProperty<String?>(nil)
private let viewDidLoadProperty = MutableProperty(())
to get a signal that has fired once on load, so i can use it in a combineLatest when user taps a button that will fire a web request with the form value to server. Since this signal merges it will give all values that change after the initial value, allowing me to send the newest value when user taps the submit button
Usage for this is usually something like
Signal.combineLatest(intialOrChangedName, initialOrChangedAge)
.sample(on:sendButtonTappedProperty.signal)
if values sent nil, i just dont include them in the web request, but i do get the other values if some of them was changed by user.
Since this is a fairly common pattern, i want to generalize it to a single function, for example
let initialOrChangedName = nameChanged.initialOrChangedState(on: viewDidLoadProperty)
I've tried writing it
extension MutableProperty where Value: OptionalProtocol {
public func initialOrChangedState(on viewDidLoadProperty: MutableProperty<Void>) -> Signal<Value?, Error> {
return Signal.merge(self.signal.map(Optional.init),
viewDidLoadProperty.signal.map { _ in nil})
}
}
Which looks good on paper, but will return String?? for the example given, and does not work.
I've also tried writing it as a static function on Signal, but with no luck.
I guess it should be something like this:
extension MutableProperty where Value: OptionalProtocol {
public func initialOrChangedState(on viewDidLoadProperty: MutableProperty<Void>) -> Signal<Value?, Error> {
return self.signal.map({Optional<Value>($0)}).merge(with: viewDidLoadProperty.signal.map({_ in nil}))
}
}
But, whats the point of using viewDidLoadProperty? Actually, if you subscribe to your signal at the end of viewDidLoad(), you don't even need such a property and as a result you wont need that merge() thing and you wont need to extend MutableProperty protocol.
So, all you need to do is something like:
submitButton.reactive.controlEvents(.touchUpInside).observer(on: UIScheduler()).observeValues({ _ in
readInputs()
launchRequest()
})
I might be misunderstanding so forgive me, if I am. But I think something like this might help. I have this method in a library I’ve written.
public func to<T>(_ value: T) -> Signal<T, Error> {
self.map { _ in value }
}
which allows you to do something like this
let voidProperty = MutableProperty(())
let nilStringSignal: Signal<String?, Never> = voidProperty.signal.to(nil)
So then maybe your case could be something like this, which leans a bit on type inference
nameChanged.signal.merge(with: self.viewDidLoadProperty.signal.to(nil))
I know maybe that’s not quite as concise as you want. Working with generics like optionals in signals can sometimes make the type wrangling a bit frustrating 😅

Subclassing UserDefaults

tldr; why do we always use UserDefaults.standard instead of subclassing UserDefaults to make something that more precisely fits our needs?
Has anyone out there subclassed UserDefaults before? Or is that considered bad practice?
Say, for example, that we make a ColorDefaults subclass of UserDefaults. When the app, the ColorDefaults object is instantiated, and that object loads all its own data. And the loaded data can then by sent to an appropriate object via delegation, or made universally available via a singleton.
My running theory is that UserDefaults is only meant to store relatively amounts of data, so having to use a singleton enforces that idea.
Bottom line: do we use UserDefaults.standard because:
subclassing is frowned upon
we're supposed to avoid saving too much data to UserDefaults in general
there's just not much value in subclassing anyway?
pretty much anything else.
Your ColorDefaults should not be a subclass of UserDefaults. It should be a plain struct or class with computed properties that are backed by UserDefaults.
Here is an example using static properties but you could refactor this to use a singleton class instead.
struct ColorDefaults {
static var someDefault: String {
get {
return UserDefaults.standard.string(forKey: "someKey") ?? "some initial value"
}
set {
UserDefaults.standard.set(newValue, forKey: "someKey")
}
}
}
let someVal = ColorDefaults.someDefault // read
ColorDefaults.someDefault = "hello" // write
This would also be useful if one of your defaults was more complicated and needed to be encoded/decoded for UserDefaults. The logic goes in here and not all over your app.
Note that such a class should only be used to store small bits of preferences, not full blown app data.
User defaults are a system of storage on file. There is little sense in subclassing unless you want to change some of its logic. But you can create multiple suits like UserDefaults(suiteName: String). What do you expect you would do with subclassing? You could simply just globally define let myDefaults = UserDefaults(suiteName: String) and use it anywhere. I guess you could use methods like
class MyDefaults: UserDefaults {
func saveName(_ name: String) {
setValue(name, forKey: "name_key")
}
}
But then again it might make more sense to just create an extension
extension UserDefaults {
func saveName(_ name: String) {
setValue(name, forKey: "name_key")
}
}
Or make it a bit more complex:
extension UserDefaults {
struct User {
static let defaults = UserDefaults(suiteName: "User")
static func saveName(_ name: String) {
defaults.setValue(name, forKey: "name")
}
}
struct General {
static let defaults = UserDefaults.standard
static func saveLastOpened(date: Date) {
defaults.setValue(date, forKey: "last_opened")
}
}
}
But all of these have one fatal flow: Now you are dependent on using user defaults within the app. At some point you may find the need to rather save these data in some other form like a local JSON file synced with iCloud. I guess UserDefaults.User could be modified to do so but would be very ugly. What we want is not UserDefaults.User.saveName("My name") but User.saveName("My name"). From the interface perspective we do not care where this user name is saved and if a new system is introduced to save these data we don't want the change in interface.
In other words, imagine you are using UserDefaults.User.saveName on 100 places in your application and now want to use another system for saving user name. You will now need to change your code on 100 places to use AnotherSystem.User.saveName while if you simply use User.saveName the interface is still valid.
So the bottom line is there is no sense in (extensively) modifying, extending or subclassing UserDefaults because it is better creating a system that wraps UserDefaults and may later be changed to any other system.
Seems you are looking for something like this
class ColorDefaults : NSObject
{
/// Save Data
class func saveDataInDefaultForKey(_ key: String, _ data: Any){
UserDefaults.standard.set(data, forKey: key)
}
/// Retrieve data
class func retrieveDataFromDefaultsWithKey(_ key: String) -> Any {
return UserDefaults.standard.value(forKey: key) as Any
}
}
Save and get data:
/// Save Data
ColorDefaults.saveDataInDefaultForKey("myArray", myArray)
ColorDefaults.saveDataInDefaultForKey("myString", myString)
/// Get Data
if let valueString = ColorDefaults.retrieveDataFromDefaultsWithKey("myString") as? String {
print("Saved Value String: \(valueString)")
}
else {
print("Error retrieving myString")
}
if let valueArray = ColorDefaults.retrieveDataFromDefaultsWithKey("myArray") as? [String] {
print("Saved Value Array: \(valueArray)")
}
else{
print("Error retrieving myArray")
}
Output: