Custom Type in MacOS document-based app in AppKit

Hi everybody,

I’m try to create an editor that open a custom type of file. (called webcodeproj) and different of others kind of file, like html, css, javascript, php and so on how can I do it?

in Apple documentation, if I understand well, is written that if you want create a new type of file you have to add an Exported Type Identifier. if you want to read a known type you have insert it in document Type and in Imported Type identifier.

but also if I do it, if I try to open a file with my extension (webcodeproj), in the dialog panel the file is not selectable. why?

In the MacOS by Tutorial, is explained but also the start project is different. in fact, if you create a new document-based project and try to safe, the app give you an error.

Can you make an updated tutorial in which explain how create an document-based app that create a custom type file and can read existing type files?

@sarah

Hi,

What version of Xcode & macOS are you using? I have just built a new document-based app in macOS 12.5 with Xcode 13.4.1 and the template works fine, saving and re-opening the default .exampletext files.

To create a custom file type I do not see any need to use the Exported Type Identifiers. In macOS by Tutorials, the Markdown files are a custom file type.

In Document Types and Imported Type Identifiers, set up your custom identifier e.g. com.mydomain.webcodeproj

In Imported Type Identifiers, add the file extension for this file type. Most importantly, you need to say what base type this conforms to. In my test, I set the file to conform to “public.text”. The documentation for Apple’s Uniform Type Identifiers has moved to Apple Developer Documentation.

I was then able to save text files with the extension .webcodeproj and read them back in again.

Next I added support for HTML files, which are a standard file type. The files are both text based, so can be opened and saved using the same FileDocument and DocumentGroup. If you had something like an image file, you would need to create a new DocumentGroup and new FileDocument to handle it.

My sample app is at GitHub - trozware/document-app-sample: Sample Mac document-based app with custom file type if you want to see how I did this.

Hope this helps,
Sarah

Hi @Sarah thank you for your help.
both my Xcode and my os are last version.
the problem is that the project that I create is made in AppKit with storyboard. not in swiftUI. in fact if you se the official documentation, to develop document-based app for App and in Swift UI are different because the code is different.

the file document in AppKit project has this two methods:

override func data(ofType typeName: String) throws -> Data {
        // Insert code here to write your document to data of the specified type, throwing an error in case of failure.
        // Alternatively, you could remove this method and override fileWrapper(ofType:), write(to:ofType:), or write(to:ofType:for:originalContentsURL:) instead.
        throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
    }

    override func read(from data: Data, ofType typeName: String) throws {
        // Insert code here to read your document from the given data of the specified type, throwing an error in case of failure.
        // Alternatively, you could remove this method and override read(from:ofType:) instead.
        // If you do, you should also override isEntireFileLoaded to return false if the contents are lazily loaded.
        throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)
    }

so. if I write or open a file I get an error.

but i’v also an other question:
you say this:

Is it required? why is it important?

because, I’ve followed a Japanese tutorial that insert in “conform To” field 'public.data" and also in this case it works. but in another tutorial this nothing is put. and the project works. it must be said that the Xcode of that project is old. so it can be mandatory now with the latest OS, or not. So I can’t figure out if it’s something necessary now, or if there’s something I don’t know.

my question is: why is it important to tell him what the file conforms to? if I don’t tell him why in some cases it works and in some not?

Thank you

The SwiftUI document-based app template sets you up with a text editor and the document struct has a text property as well as methods of reading and writing that text by default.

The AppKit template gives you the same text-based document type set up, with the same .exampletext file extension, but it provides no UI, properties or read/write implementations.

In the 2 stubbed-out methods, you have to write your own file handling code and connect it to your own text or data property which is displayed in your UI. The default implementations for these 2 methods only throw errors as they have no other code.

The “conforms to” may be less important here because you have to do a lot of the work manually. In SwiftUI, where the framework is doing more, it needs to know the parent type for your custom file type. Is it text, image, data or what? The Uniform Type Identifiers are hierarchical. For example, the png type conforms to the image type which conforms to the data type.

For your html, css, javascript and php files, the base type will be text but for your webcodeproj files, it may be something different.

Sarah

:thinking: very interesting. thank you very much @Sarah. But now I have other questions:
you say:

so, Why in Apple documentation you can see that there are many UIT, like:

static var html: UTType {get}
static var javaScript: UTType { get }
static var phpScript: UTType { get }
static var swiftSource: UTType { get }

and so on.
a clarification must be made, however: about CSS or Java files, there isn’t UIT
but the question is: if all of this the base type is Text, why there are so many file type (the question is valid also for audio and image)? and, as you can see, What I need to do for file type that there aren’t in UIT like CSS?

Let’s go to the practical, so this post can also help others like me who want to make a document-based app in AppKit and not SwiftUI.

In the AppKit template, as you know, it has three sections:

  • Document Type
  • Exported Type
  • Imported Type

the Apple documentation reads: "When defining a type, you need to define it as an exported or imported type; this declaration indicates whether your app is the source of the type or if it supports using a type defined elsewhere, respectively. If your app uses a type that this framework provides, don’t redeclare it in your app’s bundle.
Define an exported type when your app is the canonical source of information for that type. For example, an app that uses its own proprietary document format should declare it as an exported type.
Define an imported type if your app uses a type that another app defines or if it’s a proprietary file format the system doesn’t declare. When importing a type from another app, don’t declare your own identifier; instead, use the same type identifier as the original. "

based on this, to make my app handle a custom file type (webcodeproj), and several existing types (HTML, CSS Javascript, PHP, Java, Swift and so on) what should I do in practice?

because I tried to add an imported type for Css which, according to the Apple forum, should conform to public.css,
I also put it in the Document Type, but when I open the dialog to open the files and look for a css file, this is not selectable. so how can I correctly set all this kind of files?

Last question: as you said, the read and write methods are empty and the only thing they do is throw the error. what should I write about it?

in the apple documentation the methods are implemented like this:

override func read(from data: Data, ofType typeName: String) throws {
    content.read(from: data)
}
override func data(ofType typeName: String) throws -> Data {
    return content.data()!
}

but does this apply to all types of files? or only applies to text types? but also in the case of text types, does it apply to any type of text?

p.s.
It’s a real shame that the book (MacOS by Tutorial) was made for SwiftUI and not AppKit. it would have been much more useful since, as you said yourself, Appkit doesn’t change often. It would have been much more useful to me.
Anyway, thank you very much for your patience

A lot of questions here, but I’ll try to answer some of them.

If you look up the html identifier in Apple’s docs, you’ll see that it’s identifier is “public.html” and it conforms to UTTypeText. Follow that link and you see that UTTypeText’s identifier is “public.text”.

This code can get the preferred identifiers for any file extension:

import UniformTypeIdentifiers

let fileExtension = "css"
if let typeIdentifier = UTType(filenameExtension: fileExtension) {
  print(typeIdentifier.identifier)
  for superType in typeIdentifier.supertypes {
    print(superType.identifier)
  }
}

This gives “public.css” for css files and lists all the parent types too, back to “public.item” which is the generic base type.

When you add an imported type, you also need to add a matching document type. That will allow you to select that file type in the Open dialog. I have never had to add an exported type, but it appears that the imported and exported types have some overlap, so you will need to declare one of these for each file type.

This is what I set up in my test app and I can select CSS files:

The article you already linked seems to have all the info you need, including how to handle the load and save methods: How to Develop Document based Apps on macOS Using Swift

In those methods, you can check the typeName and branch accordingly, if you need to handle different file types differently. I have no idea what format your custom “webcodeproj” file is, but every other file type you have mentioned is text, so they can all be handled the same way.

I’m sorry my book is not the book you wanted, but as AppKit doesn’t change much, the older tutorials around are still valid, so you need to search for them and see what they suggest.

Sarah

@Sarah thank you very much for your answer and your patience. I’ve put in practice your answer and it’s work perfectly. my custom webcodeproj file is not a compile file like xcodeproj. but is only a file that contains all informations about the project. for this reason I think to conform it to public.data. because I don’t want that nobody can see what it contains o edit its information.

about the tutorial the problem is, who like me who want to create a Appkit app is in a limbo. because, is true that Appkit doesn’t change frequently like iOS. but it change. So you find some tutorial, like those there are here in raywenderlich site, and 60%, 70% its the same, the other percentage don’t work or it’s change. other tutorials are too old because its written in objective-c and the tutorials that are updated, they are unuseful because are not for Appkit but in swiftUI. and maybe in a year it needs to be rewritten because swiftUI has completely changed.
Therefore it is very frustrating to develop for MacOS. If at least the tutorials that are here in raywendelich were continuously updated and continuously integrated with new tutorials such as the iOS tutorials, it would be an extra facility for those like me who want to develop for macOS in AppKit. and I’ll tell you more: this situation has unfortunately existed for up to 10 years. and it is not a way of saying

anyway, thank you very much. now I can go on with my project