I documented the problems their app was causing, sent the documentation to the offending app. They promised to fix it in their next update. In the mean time, I have deleted their app off my machine and the bugs caused by their incorrect UTI declarations are gone.
This doesn't answer the question of how to correct the mdls entry, but it is a workaround.
Post
Replies
Boosts
Views
Activity
The bugs preventing UIDocument-based UIScene-based MacCatalyst apps from working properly have been fixed in the macOS 12 betas a few weeks ago. Still don't work right in macOS 11.6.
This is fixed in macOS 11. (tested in 11.5.1)
I've had good luck providing a Formatter which just passes through its string value, in which you can override methods like
func isPartialStringValid(_ partialStringPtr: AutoreleasingUnsafeMutablePointer<NSString>,
proposedSelectedRange proposedSelRangePtr: NSRangePointer?,
originalString origString: String,
originalSelectedRange origSelRange: NSRange,
errorDescription error: AutoreleasingUnsafeMutablePointer<NSString?>?) -> Bool
and do whatever you want with the user's entered string, change it, move the selection, etc...
You'll have to be more precise about the context in which you're placing this code. For instance addChild(_:) is a method on a UIViewController, and not on a UIView. But frame is a property on a UIView, not a UIViewController. So I don't know where you're getting the frame from on the right hand side of the = operator on line 4. If it's the parent view controller's view frame, then... frame is the view's position in its super view's coordinate system, whereas bounds is its position in its own coordinate system. So depending on frame, maybe you're covering it up? But if you're trying to add the hosting controller onto the navigation stack, then you should be using pushViewController(_:_:) not addChild(_:). And then, if you have navigationBarHidden(_:) (which we don't know is not the case from the code you posted) in your swiftUI view, who knows, maybe the hosting controller is setting that property correctly, too? And if your swift view has a navigation view in it, I don't even know how much more could go wrong. But we can't see that from your example. If you have a particular bug in particular code, please construct a minimal example that still causes the bug, and post that, and then we can tell for sure.
Removing all debug print() statements from scene/document code seems to have fixed the watchdog timer issues.
Doh! I used to remember that setting existed, probably 11 months ago when I first started investigating why this was failing. Changed that and I got the sessions coming back.
Still no idea why the doc browser gives a posix error 22 "invalid argument" when I try to ".revealDocument(at:" the url of the doc during my restoration. Can I open the document anyway?
Filed a DTS incident... It turns out that the os does not correctly handle typing command-q, whereas it does "correctly" handle using the mouse to select quit from the application menu. How does it not handle that correctly? The sandbox does not know that the file is still 'ok' to be written to. So writing out contents to autosave works, but when UIDocument goes to replace the original file with the version it wrote to the temp dir, it gets a sandbox error. This bug is still in macOS 11.2.2.
I know, I know, isn't it just a UIKeyCommand? Well, it is, but actually it's hacked somehow, because the quit command selector is terminate:, which is not a documented selector in UIKit. And if you try to make your own, it just crashes. And using a Mac obj-c plug-in to access appkit to call terminate: on the nsapplication instance doesn't do any of the things the uiscenes need done.
BUT, when I click the menu command for quit instead of the shortcut, it goes ahead and proactively discards my scene sessions, thus meaning there's never going to be a scene to restore when the app launches again. Which, I think is definitely not the expectation, but it's better than opening blank windows and never giving me a session to restore.
A year and a half of my life.... a year and a half. That's how long I've been re-writing apps to UIKit so I could more easily and more rapidly release apps than using AppKit. And the OS literally just never worked.
It appears the system is throwing the sessions away because the watch dog timer killed the app.
After 5 seconds after hitting command-q, I see this in the console.
[Lifecycle] Watchdog: App took too long to enter the background-only state. Exiting immediately! (5.0s)
[Lifecycle] Watchdog timeout. Exiting immediately. (App took too long to enter the background-only state.)
[Assert] App took too long to enter the background-only state.
When I pull up the Console console, I get a serious of incomprehensible excuses for not saving the document.
UIDocument unrecoverable error with description: You don’t have permission to save the file “Untitled.txt” in the folder “Documents”. reason: You don’t have permission.
Failed to recover from error with description: You don’t have permission to save the file “Untitled.txt” in the folder “Documents”. reason: You don’t have permission.
Why not? I opened it? I have read/write permissions in the entitlements.
Sandbox: MyAppName(5631) deny(1) file-read-data /Users/myusername/Documents/mydocument.txt
Why? If I close the doc by dismissing the view controller and calling .close directly, it saves successfully. So how did I lose permissions to save my work just because I'm quitting the app? Seems like a major major major design flaw in the OS.
Apparently, it can only be done using NSWorkspace. Which is part of AppKit. Which can't be imported into the code compiled directly into your frameworks.
However...
I heard others say code importing AppKit can be included in a "plug-in", which can be added for only the Mac-os version of your app and can be loaded at run time.
So here's my code for this:
I create a plug-in called "AppKitCompatibility".
The plug-in bundle is listed in the General pane of the project editor of my maccatalyst app, with Platforms set to "macOS" and Embed set to "Embed & Sign".
It contains 2 obj-c files.:
WorkspaceCompatibility.h
#import Foundation/Foundation.h
NS_ASSUME_NONNULL_BEGIN
@interface WorkspaceCompatibility : NSObject (void)showInFinder:(NSArrayNSURL * *)urls;
@end
NS_ASSUME_NONNULL_END
WorkspaceCompatibility.m
#import "WorkspaceCompatibility.h"
#import AppKit/AppKit.h
@implementation WorkspaceCompatibility (void)showInFinder:(NSArrayNSURL * *)urls {
[NSWorkspace.sharedWorkspace activateFileViewerSelectingURLs:urls];
}
@end
The plug-in's info.plist lists WorkspaceCompatibility as the principal class. I don't think that's strictly necessary, as the swift code below loads it by name.
Then in the swift UIKit portion of the app :
import Foundation
class MacCompatibility {
static let shared:MacCompatibility = MacCompatibility()
private init() {
}
#if targetEnvironment(macCatalyst)
//the classes loaded only live as long as the bundle instance, so the bundle must be retained for the duration of the app
private lazy var bundle:Bundle? = newBundle()
private func newBundle()-Bundle? {
let bun = Bundle(path: Bundle.main.builtInPlugInsPath?.appending("/AppKitCompatibility.bundle") ?? "")
bun?.load()
return bun
}
#endif
func revealUrlsInFinder(_ urls:[URL]) {
#if targetEnvironment(macCatalyst)
let files = urls as [NSURL]
let fileArray = files as NSArray
guard let macClass:AnyClass = bundle?.classNamed("WorkspaceCompatibility")
,let macCompatibility = macClass as AnyObject as? NSObjectProtocol //in Obj-c, classes are objects, too
else { return }
_ = macCompatibility.perform(NSSelectorFromString("showInFinder:"), with:fileArray) //so we can call a class method on the class
#endif
}
}
I normally hate singletons, but in this case the MacCompatibility is held as a singleton for 3 reasons: 1) the bundle must remain in memory as long as the code will be run, 2) it can be accessed from anywhere just as the workspace was, and 3) it's so little code, surely it's not a problem.
I have tested this, and it works. But I have not shipped it. I have heard other people say using plug-ins is supported by the MacAppStore.
In the future, I'd like to build a feature where my app has a specialized display for external screens, which would involve creating a separate window. Super easy in UIKit on iOS. Super easy in AppKit on Mac. Next to impossible with UIKit on Mac. However, someone posted a demo of how to create a "plugin" written in Objective-C, and include it only in my Mac version of the app. The objective-c bundle code #import AppKit, queries appkit and some other frameworks for screen information, creates an NSWindow with an NSViewController, and then vends its CALayer through the plugin's one method. Since CALayer is the same class in both frameworks, I can actually insert the CALayers I have from the uikit side of the app into the NSView in the NSWindow from the plugin. Of course, I have to use run-time bundle loading with objective-c runtime method calls, like performSelector:, to do that... but it works. I haven't shipped it yet. There are so many issues just getting document-based apps to work in maccatalyst, this ground-up-re-write of my app in MacCatalyst has been delayed for many months until I get those fixed.
I don't know if there's anyway to get it to work with SwiftUI; I've never tried playing with SwiftUI and CALayer together. But SwiftUI works on Mac, too, without catalyst. So you may be able to write a shared framework that relies on swiftui and create a hosting view / view controller in the plugin which vends the hosting view to the obj-c code that runs the bundle. Then you only need methods for updating your data source in both directions. Since you can't send swift structs through the obj-c protocol, maybe you could serialize it all down, kind of like xpc does for inter-process communication? I dunno. just thinking out of the box here.
As I mentioned on the other thread you referenced, I worked around this by not presenting my document's view controller until after my document had completed opening. Apparently on catalyst, the document browser vc actually dismisses itself when we present something on top of it.
re: B 2, I think maybe possibly you're calling the "import" method on the document browser view controller in response to your external open request? I think the import method on the document browser vc is for moving a file into the directory alongside the other url in the call. i.e. it doesn't mean "open me". I'm only using import when "openInPlace" is false, or when the user selects a file in a file format that I support converting from but not saving in. i.e. I have my native file format I'll call "s" and there is a public standard other apps create I'll call "x". I have "x" listed as a document format in my app, and as an imported type, and when the user selects one, I convert it into a temp file in format "s", then use "import" to bring it into the dir next to the original file, this causes the system to prompt the user to save the new file, which is a little unexpected nicety which is probably the right thing to do. But I wouldn't use "import" when the original document is in a format I would save in or the openInPlace was true.
I worked around problem B 1 by not presenting my document vc until after the document successfully opens.
so my code is create document
create document view controller
open document
in completion block, if successful open, present document view controller on top of UIDocumentBrowserViewController
I tried presenting a "spinner" view controller I created on top of the browser, but when I do the browser dismisses itself ( dismiss(... is getting called and I'm not calling it in my code) out from under the presented view controller. This seems to be related to why the repeated showing of the browser happens - something to do with the browser getting dismissed out from under the document view controller.
I think this is a UICollectionView bug which may be fixed in Big Sur.