The software is introduced

AVHider (OH NO) FileHider is a productivity software that hides your folders or files, for macOS X 10.10 and later. Baidu web disk download address, welcome you to try, and put forward suggestions for improvement! Friends with development ability can also go to Github to fork the project after contribute your code.

Specially thanks to unfamous Designer Joseph, who designed the exquisite logo for this Application!

The use of software is also very simple, basic can achieve file/folder visible/invisible one key switch, recorded a GIF animation.

Software using Demo

Development purpose

The original intention of developing this software is to hide XXX.mp4 / XXX.avi/XXX.mkv during the day, so that others can not find it. A similar software was found on the Apple Store, priced at 163 yuan, and selling well. As an engineer, I didn’t want to pay for it because I thought it could be built in a day, so I spent an evening building a similar application called FileHider.

The Secret Folder is priced at $163 on the Mac App Store

The Difference with the Secret Folder is that it has two columns in the TableView, and I think the column that shows the current file visible/invisible is the same as the NSSegmentedControl message on the right, so I removed that column.

Another difference is that the Secret Folder sets the option Require Password, which I think can be omitted, because if a person can enter the system when the user is not there, then the Password of the user is unnecessary. The purpose of FileHider is to hide files from people who have access to your computer screen but don’t have access to your computer.

I initially wanted to send a Notification when the user switched file visibility, but felt overdesigned because these notifications would remain in the Notification center if they were not removed manually, which obviously increased the likelihood that someone would know that a file was hidden.

The development process

Interface part

The interface completely mimics the Layout of Secret Folder. It is a single-page application and still uses StoryBoard to construct the interface.

Screenshot of project storyboard

The left and right columns are divided into two vertical columns, using NSSplitView, and adjust the size ratio of the left and right columns, the left side shows the file list and the list of add/remove buttons; On the right is the NSSegmentedControl that switches between file details and file hidden/visible. Lay out the components to make sure they remain in good shape after the window resize.

TableView section

The file list is displayed in TableView, which is the core part of this application. The default TableView Cell is only 17px tall, and each Cell contains a file thumbnail icon and file name, which is obviously too small, so you need to customize the Cell. In this project, I set the Cell to 30px with a file thumbnail of 24 X 24 px, which I think is a good size.

To display a TableView successfully, you need to know two things: **1. How many rows to display, 2. What each line shows. ** Like any other application, this TableView is driven by an array, filesList: [URL]. Note that this is an array of urls. The urls of file paths are defined as file://+ file paths. URL in Swift has a number of ways to get file names, pathnames, thumbnails of corresponding files based on the full path, details of files, and so on. For details, please refer to the official API documentation.

Data persistence

For this application, the user for a file operations is not a one-time hide is finished, it need to reserve the right to recover for visible, apparently allows users to remember which files are hidden, even hidden under which path is not realistic, so you need to data persistence, ensure that the next time the user opens the application can know which file is a hidden history. Because a file with a prior record might need to be hidden again.

There are many options for data persistence, the most typical of which are the heavy Core Data and lightweight userDefaults. Since the path to the list of files is usually not very long, I chose the relatively lightweight userDefaults.

Attempt to set a non-property-list object as an NSUserDefaults error when using userDefaults to store filesList arrays of URL types. NSUserDefaults only supports NSArray, NSDictionary, NSString, NSData, NSNumber, and NSDate data types. Most of the solutions on the web suggest encoding the array as NSData and storing it. I consider that urls and strings are easy to swap, so I convert them to String arrays for storage.

// String -> URL
	override func viewDidLoad(a) {
		let defaults = UserDefaults.standard
        if let filesListFromUserDefaults = defaults.array(forKey: "filesPath") {var tmpFilePath : [String] = filesListFromUserDefaults as! [String]
            for str in tmpFilePath{
                self.filesList.append(URL(string: str)!) }}}// URL -> String
	override func viewWillDisappear(a) {
        let defaults = UserDefaults.standard
        var array : [String] = []
        for url in filesList{
            array.append(url.absoluteString)
        }
        defaults.set(array, forKey: "filesPath")}Copy the code

Transposition between URL and String array

The timing of the conversion is important because it improves the performance of the application. String->URL this direction only happens when the application is open and the view is loaded; The URL->String direction is triggered once when the view disappears after the application is closed.

File list increment

The file increment is currently implemented by the relatively simple NSOpenPanel, which is obviously not Mac, and the drag-and-drop solution is more elegant.

@IBAction func selectFile(_ sender: Any) {
        
        let openPanel = NSOpenPanel()
        
        openPanel.message = "Please select file to Hide"
        openPanel.canChooseDirectories = true
        // openPanel.allowsMultipleSelection = true
        
        openPanel.beginSheetModal(for: view.window! , completionHandler: {(result)in
            if result == NSModalResponseOK{
                self.selectedFolder = openPanel.url! }})}Copy the code

Delete the file list

List of files to delete is still filesList of mentioned above, is obtained by the tableViewSelectionDidChange tableviewDelegate method need to delete the element index. Note that you need to add judgment to ensure that elements are currently selected. (If no element is selected, the index value will be -1, which may crash the application.)

The tableView.reloadData () method is called to update the view whenever the list of files is added or deleted.

Hidden and unhidden implementations

There are many ways to hide a file on Unix systems, and you can even encrypt the file. The easiest way I can think of is to put a. In front of the original file and hide it in place in the original path with mv XXX.mp4.xxx.mp4. That’s what the software is designed to do, to hide files from people who have a chance to pass by your computer, but don’t have a chance to actually operate your computer.

Simulating console execution is done through Process(). There were some potholes, and unfortunately I stepped on them all.

The first pitfall is that the urls of normal files and folders are different, folders end in /, and normal files don’t. To get the path and filename, I call string.ponents (separatedBy: “/”) method, then the folder name exists in the method to obtain the array of the penultimate item; The names of other ordinary files are stored in the last item of the array.

The second reason is that when the user opens the application for a different time, the parameter setting mode of executing mv needs to be discussed in four cases. This is also the hole dug by the previous fileList in order to ensure the efficiency of the application. Sure enough, everything has two sides

Drag & drop in FileHider

FileHider only needs to implement half of Drag & Drop, because it only needs to accept externally dragged files and get the file path to add them to the list of hidden files.

Drag & drop in FileHider

A review of the Drag & Drop API documentation shows that its design is similar to that of D3JS in that both provide hooks for controlling the full life cycle of an action. But there seems to be more hooks available in macOS, such as monitoring for dragging things in and out without releasing them.

override func draggingExited(_ sender: NSDraggingInfo?) {
  isReceivingDrag = false
}
Copy the code

Corresponding to this, there is a hook when it first comes in (draggingEntered).

override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation{}Copy the code

For FileHider, we need to specify the TableView as the end of the Drag & Drop event, specify the acceptable file type, and after the Drag ends, get the full path of the file and add it to the TableView’s datasource array.

The concrete implementation is as follows: First generate DragDestinationView class, inherited from NSView subclass. Because NSView naturally implements the NSDraggingDestination protocol, simply override the corresponding method. Then on the StroyBoard page, specify the NSView corresponding to the end of the Drag & Drop event as DragDestinationView.

protocol FileDragDelegate : class{
   
    func didFinishDrag(_ filePath:String)
    
}

class DragDestinationView: NSView {

    weak var delegate: FileDragDelegate?
    
    override func awakeFromNib(a) {
        super.awakeFromNib()
        // Register acceptable file types
        self.register(forDraggedTypes: [NSFilenamesPboardType])}// The file goes to NSView
    override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
        let sourceDragMask = sender.draggingSourceOperationMask()
        let pboard = sender.draggingPasteboard()
        let dragTypes = pboard.types! as NSArray
        if dragTypes.contains(NSFilenamesPboardType) {
            if sourceDragMask.contains([.link]) {
                return .link
            }
            if sourceDragMask.contains([.copy]) {
                return .copy
            }
        }
        return .generic
    }
    
		// Get the data to trigger the proxy event
    override func performDragOperation(_ sender: NSDraggingInfo?)-> Bool {
        letpboard = sender? .draggingPasteboard()letdragTypes = pboard! .types!as NSArray
        if dragTypes.contains(NSFilenamesPboardType) {
            letfiles = (pboard? .propertyList(forType:NSFilenamesPboardType))! as!  Array<String>
            let numberOfFiles = files.count
            if numberOfFiles > 0 {
                let filePath = files[0] as String
                if let delegate = self.delegate {
                    NSLog("filePath \(filePath)")
                    delegate.didFinishDrag(filePath)
                }
            }
        }
        return true}}Copy the code

An Outlet for the NSView is generated in the main ViewController, and the FileDragDelegate protocol is implemented to implement the method in the protocol, that is, the logic to execute after the Drag & Drop event is complete.

extension ViewController: FileDragDelegate {
    func didFinishDrag(_ filePath:String) {
        let url = NSURL(fileURLWithPath: filePath)
        
        filesList.append(url as URL)
        print(url)
        tableview.reloadData()
        
    }
}
Copy the code

Acknowledgments and closing remarks

First of all, I would like to thank Joseph, a non-famous designer, for providing me with a beautiful logo. I would also like to thank The Secret Folder, which inspired and motivated me to make a similar software.

reference

  1. Github
  2. stackoverflow
  3. FileManager Class Tutorial for macOS
  4. APPLE STORE: Secret Folder