Implementing a cursor on both a cell and a row while using NSTableView

Hi,

I'm trying to create a Tabular experience where the user can select a row and a cell at the same time. To give a better understanding, consider the following image:

Here, the user has selected the row containing "Data 1a, Data 2a, Data 3a, Data 4a". At the same time, their cursor is on the cell containing "Data 1a". When the user presses the Right arrow key, the row continues to be selected, but the cell selection should move to the cell containing "Data 2a". On the same front, when the user is on "Data 4a" and presses the right arrow key, the row selection moves to the next one(containing the 'b' suffixed data) while the cell containing "Data 1b" should get selected.

How can this experience be achieved using NSTableView?

Answered by Claude31 in 731487022

Yes you can.

I implemented something similar in an app. nbIndividus is the number of rows, nbVariables is number of columns.

selectedRowInDataTable and selectedColInDataTable are global var in the class.

I just copy the code, you would have to adapt of course.

    func doMovement(_ mouvement: NSTextMovement) {  
        
        if selectedRowInDataTable < 0 || selectedColInDataTable < 0 { return }
        
        var newRow = selectedRowInDataTable
        var newCol = selectedColInDataTable

        switch mouvement {  //  return as down
        case .return :
            newRow += 1 
            if newRow >= nbIndividus { newRow = nbIndividus - 1 ; playLightBeep() }
            
        case .tab : 
            newCol += 1   
            if newCol >= nbVariables { newCol = 0 ; playLightBeep() } 
            
        case .backtab :
            newCol -= 1     
            if newCol < 0 { newCol = nbVariables - 1 ; playLightBeep() }

        case .down:
            newRow += 1  
            if newRow >= nbIndividus { newRow = 0 ; playLightBeep() }
            
        case .up: 
            newRow -= 1     
            if newRow < 0 { newRow = nbIndividus - 1 ; playLightBeep() }
            
        default: break
        }
        
        dataTableView?.selectRowIndexes([], byExtendingSelection: false)
        dataTableView?.editColumn(newCol, row: newRow, with: nil, select: true)
        selectedRowInDataTable = newRow
        selectedColInDataTable = newCol
    }
Accepted Answer

Yes you can.

I implemented something similar in an app. nbIndividus is the number of rows, nbVariables is number of columns.

selectedRowInDataTable and selectedColInDataTable are global var in the class.

I just copy the code, you would have to adapt of course.

    func doMovement(_ mouvement: NSTextMovement) {  
        
        if selectedRowInDataTable < 0 || selectedColInDataTable < 0 { return }
        
        var newRow = selectedRowInDataTable
        var newCol = selectedColInDataTable

        switch mouvement {  //  return as down
        case .return :
            newRow += 1 
            if newRow >= nbIndividus { newRow = nbIndividus - 1 ; playLightBeep() }
            
        case .tab : 
            newCol += 1   
            if newCol >= nbVariables { newCol = 0 ; playLightBeep() } 
            
        case .backtab :
            newCol -= 1     
            if newCol < 0 { newCol = nbVariables - 1 ; playLightBeep() }

        case .down:
            newRow += 1  
            if newRow >= nbIndividus { newRow = 0 ; playLightBeep() }
            
        case .up: 
            newRow -= 1     
            if newRow < 0 { newRow = nbIndividus - 1 ; playLightBeep() }
            
        default: break
        }
        
        dataTableView?.selectRowIndexes([], byExtendingSelection: false)
        dataTableView?.editColumn(newCol, row: newRow, with: nil, select: true)
        selectedRowInDataTable = newRow
        selectedColInDataTable = newCol
    }

I forgot to show how to call this func:

    func control(_ control: NSControl, textView: NSTextView, doCommandBy commandSelector: Selector) -> Bool {
        
        // 1. get row and column, multiplexed in sender's tag 
        guard let textField = control as? TextField else { return false }
        
        let rowIndiv = textField.tag % (256 * 256)
        let colVar = (highWordTag - rowIndiv) / (256 * 256)

        selectedRowInDataTable = rowIndiv   // a var of the class
        selectedColInDataTable = colVar     //  var of the class

        if commandSelector == #selector(NSResponder.insertNewline(_:)) {
            doMovement(.return)
            return true
        }
        if commandSelector == #selector(NSResponder.cancelOperation(_:)) {
            doMovement(.cancel)   
            return true
        }
        if commandSelector == #selector(NSResponder.moveDown(_:)) {
            doMovement(.down)  
            return true
        }
        
        if commandSelector == #selector(NSResponder.moveUp(_:)) {
            doMovement(.up)  
            return true
        }
        if commandSelector == #selector(NSResponder.insertTab(_:)) {
            doMovement(.tab)  
            return true
        }
        if commandSelector == #selector(NSResponder.insertBacktab(_:)) {
            doMovement(.backtab)  
            return true
        }
        return false
    }

tag is created as:

                cellView.textField!.tag = 256 * row + 256 * 256 * 256 * column

Hope that will be enough.

Thanks, understood the logic. How do you use 'selectedRowInDataTable' and 'selectedColInDataTable' to set up the cursor in the right cell?

Implementing a cursor on both a cell and a row while using NSTableView
 
 
Q