1

I'm trying to display an image in a table view cell view on the condition of a Boolean value.

The Boolean is a representation of the state of an object of the class "Book" where the objects are initialized:

class Book: NSObject, Codable {
    @objc dynamic var author: String
    @objc dynamic var title: String
    @objc dynamic var lentBy: String
    @objc dynamic var available: Bool {
        if lentBy == "" {
            return true
        } else {return false}
    }

    init(author: String, title: String, lentBy: String) {
        self.author = author
        self.title = title
        self.lentBy = lentBy

    }
}

If the String lentBy is not specified, the Bool available returns true: no one has lent the book and hence it should be available. Binding the available object to the table view, the respective table view cell displays either 1 or 0. Instead of 1 or 0 I would like it to display an image: NSStatusAvailable or NSStatusUnavailable.

Have a look at this: https://i.stack.imgur.com/fbsjO.png. Where the text field "Geliehen von" (lent by) is empty, the status is 1 and should display the green circle; otherwise a red circle. The green circle you see now is simply dragged into the table cell view and is non-functional. But this is the idea.

Now I'm wondering how to display the respective image view instead of the Bool 1 or 0.

The table view is constructed with the interface builder in a storyboard. If I'm trying to make changes to it programmatically, nothing gets display in the table view anymore. I suppose this is due to the set bindings. Removing the bindings just for the last column doesn't work. This is how I tried it (without implementation of the image view; I don't know how to do that programmatically):

    func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
        if tableColumn == tableView.tableColumns[2] {
            let cellIdentifier = "statusCellID"
            let cell = tableView.makeView(withIdentifier: NSUserInterfaceItemIdentifier(rawValue: cellIdentifier), owner: self) as? NSTextField

            if let cell = cell {
                cell.identifier = NSUserInterfaceItemIdentifier(rawValue: cellIdentifier)
                cell.stringValue = books[row].lentBy
            }
            return cell
        }
        return nil
    }

What's the best solution to achieve this? Could I somehow, instead of a Bool, directly return the respective, e.g. CGImage types for lentBys representation available?

P. A. Monsaille
  • 152
  • 1
  • 1
  • 10

1 Answers1

1

You are using Cocoa Bindings. This makes it very easy.

  • In Interface Builder drag an NSTableCellView with image view into the last column and delete the current one.
  • Delete the text field and set appropriate constraints for the image view.
  • Rather than viewForColumn:Row implement

    func tableView(_ tableView: NSTableView, objectValueFor tableColumn: NSTableColumn?, row: Int) -> Any? {
        return books[row]
    }
    
  • Extend the model with an image property which is driven by KVO

    class Book: NSObject, Codable {
            @objc dynamic var author: String
            @objc dynamic var title: String
            @objc dynamic var lentBy: String
    
            @objc dynamic var available: Bool {
                return lentBy.isEmpty
            }
    
            @objc dynamic var image: NSImage {
                return NSImage(named: (lentBy.isEmpty) ? NSImage.statusAvailableName : NSImage.statusUnavailableName)!
            }
    
            static func keyPathsForValuesAffectingImage() -> Set<String> { return ["lentBy"] }
    
            init(author: String, title: String, lentBy: String) {
                self.author = author
                self.title = title
                self.lentBy = lentBy
    
            }
        }
    
  • In Interface Builder bind the Value of the image view of the table cell view to Table Cell View > objectValue.image

vadian
  • 274,689
  • 30
  • 353
  • 361
  • Fantastic! Thank you so much vadian, again! I don't quite get the purpose of `static func keyPathsForValuesAffectingImage() -> Set { return ["lentBy"] }`, would you mind explaining that a bit? Also, Xcode required me to correct one little piece of your code: `NSImage.Name.statusAvailable` instead of `NSImage.statusAvailableName`. If you're ever writing a book on macOS programming, let me know ! Nochmals danke und beste Grüße in die Schweiz! – P. A. Monsaille May 13 '19 at 08:55
  • Update your Swift version. In 4.2+ it's `NSImage.statusAvailableName`. –  `keyPathsForValuesAffecting` is a key value observer. It notifies the `image` property (note the suffix `Image()`) when the value of `lentBy` (the returned key path set) changes. The `dynamic` keyword aka Cocoa Bindings does actually the same thing. – vadian May 13 '19 at 09:11