File handling can be accomplished through scripts and the command line but there is a satisfaction and practicality to the desktop metaphor that cannot be denied. This post is about implementing multiple file drag-and-drop, the non-deprecated way, from the Finder in macOS with F#.
What can you get from this?
A real Mac app created with Visual Studio for Mac and Xamarin that can process multiple files dragged into it from the Finder.
Who is this written for?
This article is written for someone having at least a little iOS/macOS SDK experience and who as an interest in life outside of the beautiful, walled garden of those SDKs.
Statically typed functional programming (FP) tends to lead to more correct software due to its logic being being more mathematical along with some additional benefits including the following:
- The compiler becomes your friend because of strong types.
- Favoring pure functions reduces side-effects, that can make debugging/maintenance difficult.
- Reduction in the overall quantity of code.
FP may also expand your ingrained imperative programming thought processes so that working with functional paradigms becomes more natural.
Of all the functional languages, F# allows targeting perhaps the widest range of mainstream platforms (iOS/Android/macOS/Windows) with native code. This increases its practical value.
And if you can handle sarcasm, there are even more reasons!
Implementing drag-and-drop in F#
When searching for multi-file drag-and-drop examples, you may come across the deprecated global variable
NSFilenamesPboardType. The equivalent Xamarin symbol is
NSPasteboard.NSFilenamesType. Since the former symbol does not exist in Swift 4, some have suggested a workaround of hardcoding the string as “NSFilenamesPboardType” or using the alternate string
In conclusion, using one of these strings
registerForDraggedTypes(_ newTypes: [NSPasteboard.PasteboardType]) should allow an
NSDraggingDestination receiver, such as an
NSCollectionView, access to multiple dragged files.
However, the global variable
NSFilenamesPboardType is deprecated according to Apple thereby suggesting that an alternative should be sought.
The symbol appears to have been introduced in 10.10, deprecated since 10.13 and will be removed after 10.14. The immediate documentation on the variable wasn’t a help in knowing what to use in place of it.
However, a diff of
NSPasteboard.h kindly provided by Matt Stevens has a suggestion that leads to a solution.
|To||Deprecated||Create multiple pasteboard items with NSPasteboardTypeFileURL or kUTTypeFileURL instead|
For convenience, it would be nice to get the file URLs into an array. That requires handing the URL type to
readObjects(forClasses:options:). The code is shown here as parting of
draggingEnded on the
NSDraggingDestination implemented in
Overriding an init in the macOS SDK
In getting started with the surrounding app that supports the drag-and-drop operations, the trickiest part was getting the constructor to override
init(coder:), the init called when bringing up a view from the storyboard.
F# has many different constructor forms and I was not able to achieve the override by having a primary constructor even when setting a matching argument. The following code, for example, was not successful.
Instead, the problem can be solved with an explicit constructor form. This differs from having a single, primary constructor by not setting any arguments in the initial constructor. This allows multiple init overrides even though I only needed to override one.
The export attribute inside the square and angled brackets, [< >], tells the compiler what init to override. Note that it uses the Objective-C form of writing method signatures where the word “with” is included.
Many Xamarin usages in F# involve translating from C# and that entails constraints that limit how something can be written. The full range of the F# language may not be available when it comes to translating code directly. For this case of constructor overriding, I don’t believe there is an another way to write this.
Making the user interface
In contrast to the previous explicit constructor form, a view controller for the UI can be made using the primary form where it takes a single argument. It has a few text fields where one of them is updated with a count of the number of files that have been dropped into the app. These are now, with the explicit constructor form, defined as mutable
let bindings and then added as subviews in a, hopefully, familiar
Passing data between components
The text field
cntText has an outlet that defines a getter and setter to be able to update the field with the count of files.
The goal here is to have a loosely coupled architecture, allowing data to be passed between components while minimizing dependencies. In general, for MVC, views should not depend on the controllers using them.
Delegation is a solution to this problem and is used throughout the iOS/macOS SDKs. It promotes composition over inheritance as a way of extending classes. Since F# is a functional language, first class functions can be used to implement delegation without the additional overhead of delegate protocols.
The key idea of delegation is that the receiver can reference the
self of the sender. I’ve left out that reference as I did not have a need for the full delegation pattern. For reference, there are nice comparative examples of delegation at Rosetta Code.
For my solution, it is the following line inside the view controller that passes in an anonymous function to update the text field containing the count of files.
This reads as the view’s update member,
UpdateView, gets a lambda function with an argument
cnt that sets the string value of the label showing the count. That’s astounding clarity for complex functionality that might take more code to express in other environments.
That’s it! The DND view stays independent from the view controller and can be used anywhere while being able to update whoever decides to adopt it.
Let bindings vs vals
One F# language constraint is that the constructor form determines if
let bindings are available in a class. When using explicit constructors,
val is used instead of
let to configure private backing variables or uninitialized fields for its members. These members can then be accessed externally.
vals serve as the backing stores for the members (properties) defined in the following code.
vals can be function types indicated by a compact signature of the form:
type -> type.
All together, the
DNDView looks like the following code, showing the property definitions along with method overrides where the dragged types are handled. The snippet leaves out the
draggingEnded method previously listed.
Handling a programmatic UI
For this example, I chose to generate a UI programmatically. I know it is possible to use the storyboard but I still have yet to determine the optimal method for using one with F#.
The goal was to have a minimal, but functional, layout. The controls were recalculated based on the window size. NSViews in general have their origin at the lower left, this is different from iOS where it is the upper left corner. Performing the layout in
viewDidLayout of the view controller allowed for maintaining the layout during such changes as window resizing. The additional code added to the view controller for this is shown below.
I really like the sub-indenting with offside formatting for mathematically arranging expressions with a binary operator, as seen above.
Refining the dynamic layout
A window controller subclass was used solely for the purpose of being able to obtain the precise height value of the windows title bar by getting
contentRect(forBounds:) and subtracting the content view frame height. The values are in units of abstract points, not pixels. The title bar height is saved into the VC.
This example demonstrated how to create a Mac app that handles multiple files dragged in from the Finder, using a non-deprecated approach, while using the F# language. It consisted of three classes:
DNDView were the latter conformed to
Working in F# for macOS requires integrated knowledge that spans multiple languages. I recommend exploring it to learn functional programming principles that can lead to practical applications. It has an elegance that can benefit everything else you do. Stepping outside of the beautiful, walled garden of iOS/macOS can be scary at first, but the perspective gained will give you wings to fly over many technical challenges.
F# reduces syntax with a mathematical kind of offside-rule formatting. It is indentation based where whitespace is significant like Python but function oriented like Scala. There are no braces for code blocks and no return statements for functions. After awhile the extra syntax required in other languages may seem strange when you have to use it again.
It’s practically required to also know C#, the home language of most Xamarin examples and the assembly browser in Visual Studio for Mac. Being two to three levels removed from native iOS/macOS gives a unique perspective that can deepen understanding of diverse software principles.
Just like with statically typed Swift, the compiler can light your way. Following its hints and errors allowed me to know enough about what is allowed to develop the project in a way that was more like solving an algebraic puzzle by satisfying one constraint at a time rather than writing out the verbose steps of an imperative recipe.
Finally, this example was only meant as an introduction and there are many possible paths to be explored.