Cross-platform runtime File IO dialogs (open file, save file, etc.)

First of all, as with all my recent Feature Requests, I would like to say “thank you” to Epic Games and the Unreal Engine team for providing me and others with such a great tool, as well as its source. I hope my feedback can help improve it.

I have recently realized there’s no easy way to create file IO windows at runtime from within Unreal. So you can’t create default “open file” or “save file” type of dialog windows in-game, which is quite necessary for file input/output in a user friendly way.

This functionality is of course available in the editor, which is done using the IDesktopPlatform interface, for example IDesktopPlatform::SaveFileDialog. This interace is then implemented in DesktopPlatformWindows/DesktopPlatformMac etc.
However, the functionality is only for the editor, unavailable at runtime in packaged builds.

Currently, at runtime, the options available are quite limited:

  • FFileHelper can save/load files from a given path, but there’s no user-friendly way to make a user select a path in a packaged build. Would be unreasonable to expect users to input path strings like it’s MS-DOS.

  • There is a plugin generously offered for free, that allows for those kinds of windows on Windows only, but that isn’t other-platform-friendly. There are other plugins for Android file IO, and others, but none offer simple native dialog windows for cross-platform development.

  • There is an answerhub question, where people offer some pretty-limited solutions, such as:

  • Creating a custom implementation with Slate and TreeView, etc. Way too much effort, when almost every target platform OS provides APIs to do this through native interfaces. A simple function to spawn window with setting for title/file type filter/single or multiple selection would be much better.

  • Using the no-longer-maintained UnrealEnginePython plugin to then import the well-known Qt UI toolkit, and use that. Even if this plugin wasn’t outdated and unsupported, it requires either running an entire Python VM (embedded python), or for the end-user to have Python installed on their system. Obviously neither of those are great just to have basic File IO interface available.

I realize this functionality isn’t top priority for a *game *engine, but as Epic itself has stated, Unreal has long outgrown that single use case, and is used in all kinds of industries now. Seems like supporting this kind of functionality, especially for an engine claiming effortles cross-platform development, makes a lot of sense.

Any opinions, advice or other feedback much welcomed. Thanks.

Just in case, I should mention it here. For Windows, the plugin mentioned in the original post works well.

For Mac, after a couple of days, I managed to make a Mac implementation for this that works at runtime, by copying code from DesktopPlatformMac.h/.cpp

First, You need to make an Enum called [FONT=Courier New]EFileDialogFlags

enum EFileDialogFlags
Single = 0x00, // No flags
Multiple = 0x01 // Allow multiple file selections

Then, start copy-pasting stuff. It’s best to wrap all the copied code (includes, classes, interfaces, function code) in

#pragma region Mac

#pragma endregion

So that the compiler will ingore this code unless you’re compiling on Mac.

At the start of the file, define a LOCTEXT namespace by calling [FONT=Courier New]#define LOCTEXT_NAMESPACE “FileDialogs”
That is needed for one of the copy pasted interfaces.

Then, You need to include the following classes:

#include "Mac/MacApplication.h"
#include "Mac/CocoaThread.h"
#include "Misc/FeedbackContextMarkup.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/Guid.h"
#include "Runtime/Core/Public/HAL/FileManager.h"
#include "Runtime/Core/Public/Misc/Paths.h"

Some might not be necessary, not sure, but I included anything from DesktopPlatformMac.h which was runtime.

From DesktopPlatformMac.cpp, copy the following:

  • Entirety of the[FONT=Courier New] FCocoaScopeContext class
  • Entirety of [FONT=Courier New]FFileDialogAccessoryView, starting from [FONT=Courier New]@interface FFileDialogAccessoryView : NSView until the second[FONT=Courier New] @end (not the end of the interface, but the end of the @implementation)
  • Almost all of [FONT=Courier New]bool FDesktopPlatformMac::FileDialogShared function, excluding (comment out these lines):

MacApplication->SetCapture( NULL );
FMacScopedSystemModalMode SystemModalScope;

All done! Now, the [FONT=Courier New]FileDialogShared functon can be called in the same way as expected, like if you need a “Save File” dialog, call [FONT=Courier New]FileDialogShared(true, nullptr,…). This will make a dialog window on Mac, and return the selected files.

To create these windows in BP, you can create some functions that are [FONT=Courier New]UFUNCTION(BlueprintCallable), which will just call this [FONT=Courier New]FileDialogShared functon[FONT=Courier New] with a nullptr for the handle.

If you need a “open folder” function as well, you can look for [FONT=Courier New]OpenDirectoryDialog in the same file. I haven’t tried implementing it, but I’m sure it’s easy enough to get a similar result.