Tulvajarvi Saukkola


NSTableView - taulukkonäkymä

SISÄLLYSLUETTELO
1.  NSTableView ja NSArrayController
2.  NSTableView ilman NSArrayControlleria
3.  Taulukon riville haluttu taustarivi

Tässä käsitellään suositeltavinta TableView:tä (View Based TableView) ja alkuosassa siten, että tietosisältö on NSArrayControllerissa. Tästä tulee eräitä rajoituksia. Katso aivan lopussa miten toimitaan ilman NSArrayControlleria. Otsakekuva on joulukuinen tulvajärvi Saukkolassa.

1.  Konffaus käyttöliittymäeditorissa

NSTableView itse:

-    Interface Builderissa raahaa ikkunaan TableView

-    Avaa Assistant editor ja ctrl-raahaa sinne nimenomaan NSTableView (ei scrollView eikä muukaan View), anna nimi ja tee outlet

-    Attributes Inspector:
    -    Content Mode: View Based
    -    Columns: sarakkeiden lukumäärä  (mukaanlukien mahdolliset piilotettavat)
    -    Column sizing: None
    -    Alternating Rows: taulukon ulkonäköön vaikuttava asia
    -    Selection: jätä kaikki tyhjiksi (ellet halua kokeilla Type Select)

-   Size Inspector: pienennä arvoja Row Height ja Cell Spacing, jos haluat tiiviimmän näkymän

Sarakkeet (NSTableColumn):

-    Identity Inspector / Identifier: jokaiselle sarakkeelle tunniste

-    Attribute Inspector
    -    Title: sarakkeen otsake
    -    State.Editable: off (vaikka netistä löytyy muutakin ohjetta)
    -    Alignment: sarakkeen otsake oikeaan reunaan jne.
    -    NSTableColumn -> ... -> Table View Cell (alin taso) -> Alignment: sarakkeen tietosisällön sijainti
    -    Sort Key: sama kuin edellä Identifier
    -    Selector:  localizedCaseInsensitiveCompare:  (ääkköset hoituu) tai Compare: luvuille

-    Size Inspector: sarakkeen leveydet

-    tarvittaessa numerosaraketta varten raahaa sinne Number Formatter, jossa voi lukuarvon määritellä haluttuun muotoon. Ilman sitä oletuksena luvut tulee esim. "12 345" eikä 12345, joten:
NumberFormatter sijoitetaan
      saraketta varten

Array Controller käsittelee tietosisällön. Raahaa sellainen työkalupakista xib-näkymän vasempaan laitaan, ja konffaa se:

-    Avaa Assistant editor ja ctrl-raahaa siihen viiva Array Controller -kuvasta, tee outlet

-    Identity Inspector:
    -    Label eli anna sille jokin kutsumanimi (esim. arrayPaikat), ei tarvitse olla sama kuin ArrayControllerin nimi
    -    Jätä tyhjäksi User Define Runtime Attributes

-    Attributes Inspector / Keys:
    -    Poista ruksi kohdasta 'Avoid Empty Selection'
    -    Lisää avaimet vastaten TableView:n sarakkeiden Identifierejä

Sarakkeiden (NSTableColumn) konffausta jatka, sarake kerrallaan:

-    Bindings inspector / Value:
    -    Bind To: oma taulukkosi eli kuvakkeen kutsumanimi IB-näytöllä
    -    Controller Key: = "arrangedObjects"
    -    Model Key Path: oman taulukkosi sarakkeen Identifier (siihen jää harmaa huutomerkki)
    -    sarakkeen oheiselle alaosiolle Binding Inspector:
  cell bindining in
      xib

Jatka NSTableView:n konffausta

-    Binding Inspector / Table Content / Content:
    -  Bind To: oma taulukkosi eli kuvakkeen kutsumanimi IB-näytöllä
    -  Controller Key = arrangedObjects

-    Binding Inspector / Table Content / Selection indexes:
    -  Bind To: oma taulukkosi eli kuvakkeen kutsumanimi IB-näytöllä
    -  Controller Key = selectionIndexes

-    Binding Inspector / Table Content / Sort Descriptors (mahdollistaa lajittelun):
    -  Bind To: oma taulukkosi eli kuvakkeen kutsumanimi IB-näytöllä
    -  Controller Key = sortDescriptors

-   Connections Inspector (ei pakollinen, riippuu siitä miten lataat sisällön):
    On tehtävä linkitys dataSource ja delegate. Ctrl-raahaa componentin renkaasta vasemmalle otsakkeseen File's Owner.     Lopputuloksen on oltava tällainen:
   Delegate and DataSource


Kaksoisklikkaus / NSTableView:

-    Tämä vain jos haluat, että taulukon rivin kaksoisklikkauksesta tulee Action

-    Bindings inspector / Double Click Argument
    -    Bind To: oma taulukkosi eli kuvakkeen kutsumanimi IB-näytöllä
    -    Controller Key = "selectedObjects"
    -    Selector Name = "riviKlik:" (muista ":")  (eli nimi ohjelmamoduulille johon koodia)

-    Bindings inspector / doubleClickTarget
    -    Bind To:  File's Owner
    -    Model Key Path = "self"
    -    Selector Name = "riviKlik:" (eli nimi ohjelmamoduulille johon koodia)

-    koodissa  func riviKlik(valitut: NSArray) kertoo mitä klikkauksessa pitäisi tehdä

Taulukon sitominen ympäristöönsä on tehtävä Bordered Scroll View:n avulla. Kaikki 4 constraintia määriteltävä.
 

Koodausesimerkkejä

Taulukkoa varten tiedot kerätään seuraavasti. Tämä esimerkki käyttää tietokantaa, mutta tiedot voi toki viedä NSArrayControlleriin vastaavalla tavalla vakiotaulukostakin. Alla olevan funktion kutsumisen jälkeen meillä on tiedot paikoillaan taulukossa. Tarvitaan luokkamuuttujat:
var db: FMDatabase?
var ehtoz = ""    // tänne on kerätty hakuehdoista SQL-lauseke
@IBOutlet var arrayPaikat: NSArrayController!


Ja tässä itse funktio:

// Näyttää hakutuloksen Paikat-taulukossa
// Tätä varten tarvitaan SQL-lause, joka on muuttujassa ehtoz
// Swift 4, FMBD versio 2.7
// Output=-1: ongelma
//    muu = löytyneiden paikkojen lukumäärä
func naytaHakutulosPaikka() -> Int
{
   if ehtoz.isEmpty
   {
       return -1
   }
   do
   {
      let rs = try db!.executeQuery(ehtoz, values: nil)
       
      // SELECT idPaikka, Paikkanimi, kpl, Aluenimi  FROM ....
      // kahdessa kentässä Int, kahdessa String
       
      var n = 0
       
      while rs.next()
      {
         let idi = Int(rs.int(forColumn: "idPaikka"))  // jos kannassa NULL, saadaan 0
           
         let z = rs.string(forColumn: "Paikkanimi")
         let paikkaz = (z == nil) ? " " : z!
           
         let az = rs.string(forColumn: "AlueNimi")
         let aluez = (az == nil) ? " " : az!
       
         let kpl = Int(rs.int(forColumn: "kpl"))
                       
         n += 1
           
        // viedään Int, String, String, Int
         let sisus =
["idPai":idi, "Paikka":paikkaz, "Alue":aluez, "Kpl":kpl] as [String : Any]
          arrayPaikat.addObject(sisus)           
      }
      catch
      {
         print("ongelma \(error.localizedDescription)")
         return -1
      }
   }
   return n
}

Jos on tarvetta vain päivittää taulukossa yksittäinen rivi, niin muokkaa kysely siten että tuloksena vain yksi rivi, ja tietojen haun jälkeen:
arrayPaikat.remove(atArrangedObjectIndex: irivi)
let sisus = ["idPai":idi, "Paikka":paikkaz, "Alue":aluez, "Kpl":kpl]
arrayPaikat.insert(sisus, atArrangedObjectIndex: irivi)

Näytölle voidaan päivittää yhtä riviä niin että valinta säilyyy ja rivi pysyy paikoillaan:
// Ensin ota talteen valitun rivin indeksi:
let valittu = arrayPaikat.selectionIndex
// oltava  valittu != Foundation.NSNotFound
// nyt hae korjattu data taulukkoon arrayPaikat, ks edellä
// korjatun tiedon näyttäminen:
tableviewPaikat.setNeedsDisplay(tableviewPaikat.rect(ofRow: valittu))

Tyhjennetään näytöltä vanhat hakutulokset
arrayTau.content = nil

Rivin valinta ohjelmallisesti (jälkimmäinen tekee taulukon valituksi ja rivin siniseksi):
arrayPaikat.setSelectionIndex(ii)
// ennen ylläolevaa tee tarkistus ii < tableviewPaikat.numberOfRows
// jos ii on liian suuri, kaikki valinnat poistuvat
tableviewPaikat.window!.makeFirstResponder(tableviewPaikat)


Näytön skrollaus niin, että valittu saadaan keskelle. Tässä näytöllä kerrallaan 10 riviä:
let riveja = tableviewTiedot.numberOfRows
let iri = (valittu + 6 > riveja) ? riveja - 1 : valittu + 5
tableviewTiedot.scrollRowToVisible(iri)


Taulukon rivien lukumäärä (tuloksena Int), vaihtoehdot:
let kpl = (arrayTiedot.arrangedObjects as AnyObject).count!
let kpl2 = (arrayTiedot.arrangedObjects as! [Any]).count
let kpl3 = tableviewTiedot.numberOfRows


Tarkistetaan onko jotain ylipäätään valittuna ja sitten käsitellään tietoja
let indi = arrayTiedot.selectionIndex
if indi==Foundation.NSNotFound
{
    print("Ohjelmointivirhe tai rivi valitsematta")
    return false
}
let kk = arrayTiedot.selectedObjects[0] as! NSDictionary
let zz = kk.value(forKey: "aluenimi") as! String 
// jne.

Kerätään sarakkeen "idPaikka" tiedot talteen: 
let tempx: NSArray = arrayPaikat.arrangedObjects as! NSArray
if tempx.count == 0
{
   // ei tuloksia
}
let keyz: AnyObject = "idPaikka" as AnyObject
for i in 0..<tempx.count
{
   let xxx = tempx.object(at: i) as! NSDictionary
   let z: String = xxx.object(forKey: keyz) as! String
  // tee z:llä jotain
}


Taulukon otsakkeen vaihtaminen:
let sara = tableTaulu.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "idi"))
sara?.headerCell.attributedStringValue = NSAttributedString(string: "idLaatu", attributes: nil)

edelläoleva sijoittaa otsikon vasempaan reunaan, laitetaan keskelle:
let paragraph = NSMutableParagraphStyle()
paragraph.alignment = .center
sara?.headerCell.attributedStringValue = NSAttributedString(string: "idLaatu", attributes: [.paragraphStyle: paragraph])

Taulukon sarakkeen piiloittaminen ohjelmallisesti:
sara?.isHidden = true  //ks sara edeltä

Taulukon sarakkeelle saa määriteltyä haluamansa fontin. Tämä vaikuttaa taulukon sisältöön, ei otsakkeisiin:
let sara = tableviewHakutulos.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Paikka"))
let cc: NSCell = sara!.dataCell as! NSCell
cc.font = NSFont(name: "Arial", size: 11.0)


Rivin kaksoisklikkauksen pyydystys:
Laita windowDidLoad()-funktioon:
// taulukon rivin doubleclick
tableviewTulos.target = self
tableviewTulos.doubleAction = #selector(havaDoubleklik)

Ja sitten funktioksi muiden joukkoon:
@objc func havaDoubleklik(_ sender:AnyObject)
{
    // tänne mitä pitää tapahtua valitun rivin tiedoilla
}


Taulukon rivin valitsemalla halutaan tapahtuvan jotain (kolme vaihtoehtoa):

Vaihtoehto 1 (#selector), jos rivin valinta tapahtuu hiirellä klikkaamalla.
Laita windowDidLoad() -funktioon seuraava rivi:
tableviewTulos.action = #selector(onTTItemClicked)
ja vastaava action-funktio luokan muiden funktioiden joukkoon:
@objc private func onTTItemClicked()
{
    let tt = tableviewTulos!
    let iri = tt.clickedRow
    let ito = tt.clickedColumn
    if iri >= 0    // iri = -1, jos klikattiin otsaketta
    {
        // iri, ito: tee näiden perusteella jotain
    }
}

Vaihtoehto 2 (muuttujan tarkkailija), jos rivin valinta tapahtuu hiirellä klikkaamalla tai siirtymällä nuolinäppäimillä riviltä toiselle. Laita windowDidLoad() -funktioon allaoleva rivi:
arrayTulos.addObserver(self, forKeyPath: "selectionIndexes", options:NSKeyValueObservingOptions.new, context:nil)

Tarkkailun tulos hyödynnetään korvaamalla (override) tarkkailijan action omalla:
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?)
{
   if keyPath == "selectionIndexes"
   {
      let rivi = tableviewTulos.selectedRow
      if rivi == -1   // tilanne kun taulukko tyhjennetty
      {
         print("TYHJÄ rivi")
         return
      }
      let kk = arrayTulos.selectedObjects[0] as! NSDictionary
      let failiz = kk.value(forKey: "Pvm") as! String
      //  tee tuloksella jotain
   }
}


Jos luokassa tarkkaillaan useaa muuttujaa, tulee vain yksi ylläoleva funktio. Input keyPath sisältää muuttujan nimen, edellä "selectionIndexes". Edellä olevaa funktiota tulee kutsuttua usein, esimerkiksi kun taulukkoon arrayPaikat lisätään uusi rivi (se tulee automaattisesti valituksi) tai kun taulukko tyhjennetään. Jos haluat välttää ettei rivin lisäämisestä aiheudu määrittelemiäsi toimenpiteita, käytä apumuuttujaa (esim. var ohita = true) oikeissa paikoissa.

Tarkkailija pitää poistaa sitten, kun sitä ei enää tarvita, esim luokan deinitiliaisoinnin yhteydessä:
deinit
{
   arrayTulos.removeObserver(self, forKeyPath: "selectionIndexes", context: nil)
}


Vaihtoehto 3 (delegaattifunktio), jos rivin valinta tapahtuu hiirellä klikkaamalla tai siirtymällä nuolinäppäimillä riviltä toiselle. Lisää luokan määrittelyyn delegaatti:
class Vak_henk: NSWindowController, NSTableViewDelegate
Laita windowDidLoad() -funktioon allaoleva rivi:
tableviewTiedot.delegate = self
Lisää delegaattifunktio luokan muiden funktioiden joukkoon:
func tableViewSelectionDidChange(_ notification: Notification)
{
    // Nouki valittu rivi kuten edellä on kuvattu
    // Tee homma mitä valinnan yhteydessä tapahtuu
}




*****************************************************************************************

2. NSTableView ilman NSArrayControlleria

Alla oleva menettely ei sovellu suoraan silloin, jos ikkunassa on useita taulukoita. Ilman NSArrayControlleria pitäisi jokainen taulukko sijoittaa omaan NSViewController -luokkaansa. Sitä ei ole seuraavassa esitelty.

NSArrayController mutkistaa ohjelmointia, jos on tarvetta esimerkiksi seuraavaan:
-    halutaan lajitella rivit kahden tai useamman sarakkeen tietojen perusteella
-    halutaan käyttää samaa tietolähdettä useaan eri taulukkoon, tai esimerkiksi NSTableView:hen ja NSCollectionView:hen
-    halutaan pitää sama rivi valittuna tietojen muokkausten jälkeen

Lisäksi taulukot tuntuvat toimivan luikkaammin ilman NSArrayControlleria.

Interface Builderissa rakenna taulukko ja sarakkeet:

NSTableView itse:

-    Interface Builderissa raahaa ikkunaan TableView

-    Avaa Assistant editor ja ctrl-raahaa sinne nimenomaan NSTableView (ei scrollView eikä muukaan View), anna nimi (esim. tableviewTulos) ja tee outlet

-    Attributes Inspector:
    -    Content Mode: View Based
    -    Columns: sarakkeiden lukumäärä  (mukaanlukien mahdolliset invisible)
    -    Column sizing: None
    -    Alternating Rows: taulukon ulkonäköön vaikuttava asia
    -    Selection: jätä kaikki tyhjäksi
    -   Size Inspector: pienennä arvoja Row Height ja Cell Spacing, jos haluat tiivimpmän näkymän

Sarakkeet (NSTableColumn):

-    Identity Inspector / Identifier: jokaiselle sarakkeelle tunniste

-    Attribute Inspector
    -    Title: sarakkeen otsake
    -    State.Editable: off (vaikka netistä löytyy muutakin ohjetta)
    -    Alignment: sarakkeen otsake oikeaan reunaan jne.
    -    NSTableColumn -> ... -> Table View Cell (alin taso) -> Alignment: sarakkeen tietosisällön sijainti
    -    Sort Key ja Selector: ei mitään, koodaat itse, ks. alempana
 
-    Size Inspector: sarakkeen leveydet

-    tarvittaessa numerosaraketta varten raahaa sinne Number Formatter, jossa voi lukuarvon määritellä haluttuun muotoon. Ilman sitä oletuksena luvut tulee esim. "12 345" eikä 12345:
NumberFormatter sijoitetaan
      saraketta varten

 
Koodaus:

Luokan nimessä määrittele TableView:tä varten tieto, että taulukkonäkymän ja tietolähteen tuki löytyvät samasta luokasta:
class Tabikku: NSWindowController, NSTableViewDelegate, NSTableViewDataSource

Luokkamuuttujiksi myös sarakkeiden tämän hetken lajittelujärjestys ja taulukko, josta data otetaan (luokkien taulukko):
var sortnimiAscending = true
var sortpvmAscending = true
var kuvat = [Kuvainfo]()
@IBOutlet weak var tableviewTulos: NSTableView!


Tässä Kuvainfo on luokka, joka pitää erikseen määritellä, esimerkki alla. Luokan muuttujista ei tarvitse kaikkia käyttää taulukkonäkymässä, käytettävät kerrotaan koodissa:
class Kuvainfo: NSObject
{
    @objc var idKuva: Int = -1
    @objc var Pvm: String = ""
    @objc var Nimi: String = ""
    @objc var kuva: NSImage? = nil
    @objc var polkuz: String = ""

    @objc init(idKuva: Int, .....)
    {
        ....
    }
}


Luokan muodostajaan windowDidLoad() lisää rivit:
tableviewTulos.delegate = self   // tämäkin on tarpeen!
tableviewTulos.dataSource = self


Seuraavat taulukkonäkymän tukifunktiot tarvitaan (eka ja kolmas ovat pakollisia):
func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {  }
func tableView(_ tableView: NSTableView, mouseDownInHeaderOf tableColumn: NSTableColumn) {  }
func numberOfRows(in tableView: NSTableView) -> Int
{
    return kuvat.count
}


Ensimmäinen vie sisällön taulukosta kuvat taulukkonäkymään.
Toinen lajittelee taulukon sen sarakkeen mukaan, jonka otsaketta käyttäjä klikkaa.
Kolmas kertoo taulukon koon.
Näillä funktioilla taustajärjestelmät hoitavat taulukkonäkymää ja käyttäjän toimenpiteitä.

Funktioiden sisällä on minun omia muuttujiani ja vakioitani seuraavasti:

func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any?
{
    var result: Any? = nil
    let saraid = tableColumn!.identifier
    if saraid.rawValue == "idKuva"
    {
        result = kuvat[row].idKuva
    }
    // ... jne.
    return result
}

func tableView(_ tableView: NSTableView, mouseDownInHeaderOf tableColumn: NSTableColumn)
{
    let saraid = tableColumn.identifier
    if saraid.rawValue == "Nimi"
    {
        if sortnimiAscending
        {
            //kuvat.sort(by: {$0.Nimi < $1.Nimi})
            kuvat.sort(by: {$0.Nimi.localizedCaseInsensitiveCompare($1.Nimi) == ComparisonResult.orderedAscending})
        }
        else
        {
            //kuvat.sort(by: {$0.Nimi > $1.Nimi})
            kuvat.sort(by: {$0.Nimi.localizedCaseInsensitiveCompare($1.Nimi) == ComparisonResult.orderedDescending})
        }
        sortnimiAscending = !sortnimiAscending
        tableviewTulos.reloadData()   // muutokset näkyviin
    }
    // ... jne. muut sarakkeet
}

Sovelluksen alussa viedään taulukkoon kuvat sisältö, ja kun aloitusfunktiot on suoritettu, myös taulukkonäkymässä ovat tiedot paikoillaan. Taulukon kuvat tietoja voi myöhemmin muuttaa vaikkapa vain yhden tiedon osalta (esimerkiksi päivämäärää),  minkä jälkeen seuraavalla komennolla taulukkonäkymä päivittyy ja säilyy samalla kohdalla:
tableviewTulos.reloadData()

Taulukot tyhjennetään kokonaan näin:
kuvat.removeAll()
tableviewTulos.reloadData()


Mikä rivi on taulukossa valittuna:
let kk = tableviewTulos.selectedRow
let irivi = (kk == -1) ? -1 : kuvat[kk].idKuva
// jos tarkoitus oli että aina on joku valittuna, niin saat kk-arvosta tehtyä virheilmoituksen

Tehdään valituksi rivi kuvat[iri]:
tableviewTulos.selectRowIndexes(NSIndexSet(index: i) as IndexSet, byExtendingSelection: false)
// tehdään taulukko valituksi jolloin valitusta rivistä sininen:
tableviewTulos.window!.makeFirstResponder(tableviewTulos)


Näytön skrollaus niin, että valittu saadaan keskelle. Tässä näytöllä kerrallaan 10 riviä:
let riveja = tableviewTulos.numberOfRows
let iri = (valittu + 6 > riveja) ? riveja - 1 : valittu + 5
tableviewTulos.scrollRowToVisible(iri)


Taulukon rivien lukumäärä (tuloksena Int), vaihtoehdot:
let kpl = tableviewTulos.numberOfRows

Taulukon sarakkeelle saa määriteltyä haluamansa fontin. Tämä vaikuttaa taulukon sisältöön, ei otsakkeisiin:
let sara = tableviewHakutulos.tableColumn(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: "Paikka"))
let cc: NSCell = sara!.dataCell as! NSCell
cc.font = NSFont(name: "Arial", size: 11.0)


Rivin kaksoisklikkauksen pyydystys:
Laita windowDidLoad()-funktioon:
// taulukon rivin doubleclick
tableviewTulos.target = self
tableviewTulos.doubleAction = #selector(havaDoubleklik)

Ja sitten funktioksi muiden joukkoon:
@objc func havaDoubleklik(_ sender:AnyObject)
{
    // tänne mitä pitää tapahtua valitun rivin tiedoilla
}


Taulukon rivin valitsemalla halutaan tapahtuvan jotain (kaksi vaihtoehtoa):

Vaihtoehto 1 (delegaattifunktio), jos rivin valinta tapahtuu hiirellä klikkaamalla tai siirtymällä nuolinäppäimillä riviltä toiselle. Lisää luokan määrittelyyn delegaatti:
class Vak_henk: NSWindowController, NSTableViewDelegate
Laita windowDidLoad() -funktioon allaoleva rivi:
tableviewTiedot.delegate = self
Lisää delegaattifunktio luokan muiden funktioiden joukkoon:
func tableViewSelectionDidChange(_ notification: Notification)
{
    // Nouki valittu rivi kuten edellä on kuvattu
    // Tee homma mitä valinnan yhteydessä tapahtuu
}


Vaihtoehto 2 (#selector), jos rivin valinta tapahtuu hiirellä klikkaamalla.
Laita windowDidLoad() -funktioon seuraava rivi:
tableviewTulos.action = #selector(onTTItemClicked)
ja vastaava action-funktio luokan muiden funktioiden joukkoon:
@objc private func onTTItemClicked()
{
    let tt = tableviewTulos!
    let iri = tt.clickedRow
    let ito = tt.clickedColumn
    if iri >= 0    // iri = -1, jos klikattiin otsaketta
    {
        // iri, ito: tee näiden perusteella jotain
    }
}


********************************************************************************

3. Taulukon riville haluttu taustaväri


Tarvitaan delegaatti  NSTableViewDelegate ja sille funktio
func tableView(_ tableView: NSTableView, rowViewForRow row: Int) -> NSTableRowView?
{
    return OmaRowView()
}

Taustaväri järjestetään toimenpiteellä "Subclassing of NSTableRowView":
class OmaRowView: NSTableRowView
{
    //-------------------------------------------
    // 18.12.2021   R12
    // Riville haluttu taustaväri
    override func drawSelection(in dirtyRect: NSRect)
    {
        if self.selectionHighlightStyle != .none
        {
            let selectionRect = NSInsetRect(self.bounds, 1.0, 1.0) //, 2.5, 2.5)
               
            let valittu = NSColor(red: 240/255, green: 230/255, blue: 140/255, alpha: 1.0)
            valittu.setFill()

            let selectionPath = NSBezierPath.init(roundedRect: selectionRect, xRadius: 0, yRadius: 0)
            selectionPath.fill()
        }   
    }
}

Jos lisäksi halutaan, että teksti ei ole valinnassa inverted (valkoinen), niin edellä olevaan luokkaan lisätään funktiot:
override var isEmphasized: Bool
{
    set {}
    get { return false }
}

override var selectionHighlightStyle: NSTableView.SelectionHighlightStyle
{
    set {}
    get { return .regular }
}


--------------------------------
(sivua muokattu 8.1.2021)