Detecting click on a specific NSTableViewCell

When you use NSTableView in an macOS application, there is no direct way to know a specific NSTableViewCell was clicked by the user. In my Localization Editor project I wanted the user to be able to focus a NSTextField when clicking anywhere in the NSTableViewCell it is contained in, so I had to implement it myself.

I created a new delegate extending the NSTableViewDelegate with one additional method informing about a NSTableViewCell getting clicked

protocol NSTableViewClickableDelegate: NSTableViewDelegate {
    func tableView(_ tableView: NSTableView, didClickRow row: Int, didClickColumn: Int)
}

Then I added an extension to the NSTableView to compute the index of the clicked NSTableViewCell

extension NSTableView {
    open override func mouseDown(with event: NSEvent) {
        let localLocation = self.convert(event.locationInWindow, to: nil)
        let clickedRow = self.row(at: localLocation)
        let clickedColumn = self.column(at: localLocation)

        super.mouseDown(with: event)

        guard clickedRow >= 0, clickedColumn >= 0, let delegate = self.delegate as? NSTableViewClickableDelegate else {
            return
        }

        delegate.tableView(self, didClickRow: clickedRow, didClickColumn: clickedColumn)
    }
}

To be able to use this extension you just need to implement NSTableViewClickableDelegate instead of NSTableViewDelegate and use the additional method it provides.

macos  swift 

Making copy & paste work with NSTextField

When I started working on my open-source Localization Editor, which is a macOS application, I encountered some things that were a bit strange compared to iOS development. One of those things is that copy & paste does not automatically work on a NSTextField.

To be exact, copy & paste works on a NSTextField as long as you do not delete the Edit menu from the standard Main menu. If you do that for some reason, you have to implement all the copy & paste functionality yourself.

The key to the implementation is overriding the performKeyEquivalent method and manually checking for cmd + c/v/x/z/a

final class EditableNSTextField: NSTextField {

    private let commandKey = NSEvent.ModifierFlags.command.rawValue
    private let commandShiftKey = NSEvent.ModifierFlags.command.rawValue | NSEvent.ModifierFlags.shift.rawValue

    override func performKeyEquivalent(with event: NSEvent) -> Bool {
        if event.type == NSEvent.EventType.keyDown {
            if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandKey {
                switch event.charactersIgnoringModifiers! {
                case "x":
                    if NSApp.sendAction(#selector(NSText.cut(_:)), to: nil, from: self) { return true }
                case "c":
                    if NSApp.sendAction(#selector(NSText.copy(_:)), to: nil, from: self) { return true }
                case "v":
                    if NSApp.sendAction(#selector(NSText.paste(_:)), to: nil, from: self) { return true }
                case "z":
                    if NSApp.sendAction(Selector(("undo:")), to: nil, from: self) { return true }
                case "a":
                    if NSApp.sendAction(#selector(NSResponder.selectAll(_:)), to: nil, from: self) { return true }
                default:
                    break
                }
            } else if (event.modifierFlags.rawValue & NSEvent.ModifierFlags.deviceIndependentFlagsMask.rawValue) == commandShiftKey {
                if event.charactersIgnoringModifiers == "Z" {
                    if NSApp.sendAction(Selector(("redo:")), to: nil, from: self) { return true }
                }
            }
        }
        return super.performKeyEquivalent(with: event)
    }
}
macos  swift 

Creating bootable macOS backups

If you use a Hackintosh you need a good backup solution. The chance of something going wrong is a bit higher than when running macOS on an Apple computer. Any macOS update can theoretically break your installation, you can break you Clover setup by accident or you SSD might just die.

With a good backup solution you should be up and running in a few minutes after any of those problems occur. I tried a few solutions as my requirements changed.

Initial image based backups

First I decided to use CloneZilla and backup the whole macOS SSD into an image file on another HDD every week. In case of failure I would be able to restore the image file to the SSD or any new SSD and be back up and running in about 30 minutes. I basically use macOS only for work, so restoring an at most week old backup did not seem like a problem, I would then just do a git pull and all the important data would be back.

The backups worked fine, when I bought a bigger SSD for macOS and just restored the latest backup image to it and everything went fine. I had to remember to boot CloneZilla every week and do the backup, but that was not that much of a problem, it became a weekly ritual.

The problem with image base backups

The problem is that requirements change over time, most of the time after problems you did not expect before.

Size dependent on the filesystem

CloneZilla works well with HFS+, can detect used and free space. This means it only backs up the used space, so the resulting image is not as big as your SSD and the process is fast. When you change the filesystem to APFS, for example when updating to Mojave this is no longer true. CloneZilla does not support APFS so it backs it up sector by sector, the resulting image is as big as the SSD and the process is quite slow.

Inflexibility for smaller fixes

One time I updated Clover and my Hackintosh did not boot, got stuck on some error message. The problem was that the Clover installer decided not to check OsxAptioFix3Drv-64.efi by default anymore and this module got deleted.

Doing a full restore from an image seemed like a waste of time when I just needed to restore one missing file. I used Clover on my Unibeast flash drive that I keep safe and booted the Hackintosh with it. This screwed up iMessage (different serial number). I then reinstalled Clover checking OsxAptioFix3Drv-64.efi, booted backed normally and fixed iMessage.

This made me realize that image based backups are not ideal for real world problems (you probably screw up your EFI or kexts more often that your SSD dies) and I got looking for some other solution.

Bootable macOS backups

I stumbled upon Carbon Copy Cloner; a backup tool that offers bootable backups for macOS. I bought another SSD for bootable backups and got to work.

Preparing the SSD

Once you plug in the backup SSD, you need to format it to Mac OS Extended (Journaled). Do not just format the existing partition (if any already exists), format the whole drive.

[Read More]

Simple bindable "no data" placeholder for UITableView

Almost every iOS application uses UITableView to display some kind of data and in many cases the data can be missing. In such situation you typically want to display some kind of “no data” placeholder. The placeholder fills in the empty space and can tell the user what needs to be done to get the data.

There are many ways wo implement such “no data” placeholder, including using libraries like UIEmptyState. If you just need to display a simple text message, there is an really easy way to implement it yourself, as I did in a recent project.

Showing and hiding the placeholder

The easiest way to create a “no data” placeholder for UITableView is to set it as the background view. It will be shown when there is no data in the UITableView so it will not be hidden under any other wiew.

I created a simple UITableView extension to show the placeholder with a specific message

extension UITableView {
    func setNoDataPlaceholder(_ message: String) {
        let label = UILabel(frame: CGRect(x: 0, y: 0, width: self.bounds.size.width, height: self.bounds.size.height))
        label.text = message
        // styling
        label.sizeToFit()

        self.isScrollEnabled = false
        self.backgroundView = label
        self.separatorStyle = .none
    }
}

To hide it when no longer needed I created another extension method

extension UITableView {
    func removeNoDataPlaceholder() {
        self.isScrollEnabled = true
        self.backgroundView = nil
        self.separatorStyle = .singleLine
    }
}

With this I would have to call those two extension method manually depending on the data in the UITableView which is unnecessary manual work.

[Read More]
ios  xcode  rxswift 

Switching my Hackintosh from NVIDIA to AMD because of Mojave

When I turned my computer into a Hackintosh about 2 years ago I was using an NVIDIA GTX 660 as my GPU and it worked without any additional drivers because of built-in support in Sierra. When I later bought a 4K display I could not make the GTX 660 drive the display at 4K@60Hz in macOS, although it worked well in Windows.

NVIDIA web drivers

I decided I needed a more powerful GPU anyway to play games on the new display in Windows so I bought a GTX 1060. It worked in Sierra and High Sierra thanks to the so called web drivers; GPU drivers provided my NVIDIA on their website. Without the web drivers you get no hardware acceleration, no 4K as maximum resolution, just one display working … the whole setup is basically unusable.

Those web drivers are version specific, every time the macOS build number changes after some update you need new ones (or to use a script to patch the previous ones). This is a bit annoying, you typically have to wait a few days after every macOS update for new drivers to become available and update then.

No web drivers for Mojave

When a new major version of macOS comes out, like Mojave, you cannot use web drivers for the previous version. NVIDIA needs to release new drivers and they now cannot do it without cooperation from Apple. Citing from the NVIDIA Developer forums

Developers using Macs with NVIDIA graphics cards are reporting that after upgrading from 10.13 to 10.14 (Mojave) they are experiencing rendering regressions and slow performance. Apple fully controls drivers for Mac OS. Unfortunately, NVIDIA currently cannot release a driver unless it is approved by Apple.

Apple basically blocks NVIDIA from releasing web drivers for Mojave, that is the reason the drivers are not out even now half, a year after Mojave release. If you are for example an iOS developer, XCode 10.2, the next version of Xcode, will only run on Mojave and you will not be able to use it unless you upgrade.

AMD GPUs in Mojave

Mojave natively supports some GPUs from AMD. You can buy a RX 560, RX 570, RX 580, Vega 56 or Vega 64 and it should work out of the box, no extra drivers needed. You not even have to install Lilu or Whatevergreen.

[Read More]