Hi everyone,
I’m looking for help with an issue where using insertRows to add a row to an NSTableView requires resizing the window before I can scroll to the newly added rows that extend beyond the visible area of the NSScrollView.
import Cocoa
class ViewController: NSViewController {
var data = Constants.movies
// Table components
var titleField = NSTextField()
var directorField = NSTextField()
var releaseYearField = NSTextField()
var addMovieButton = NSButton(title: "Add Movie", image: NSImage(systemSymbolName: "plus", accessibilityDescription: "")!, target: nil, action: #selector(addMovie))
var table = NSTableView()
var titleColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(Constants.titleColumnId))
var directorColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(Constants.directorColumnId))
var releaseYearColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(Constants.releaseColumnId))
// Scroll view
let scrollView = NSScrollView()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
view.wantsLayer = true
view.layer?.backgroundColor = NSColor.black.cgColor
configure()
}
private func configure() {
titleField.translatesAutoresizingMaskIntoConstraints = false
directorField.translatesAutoresizingMaskIntoConstraints = false
releaseYearField.translatesAutoresizingMaskIntoConstraints = false
addMovieButton.translatesAutoresizingMaskIntoConstraints = false
scrollView.translatesAutoresizingMaskIntoConstraints = false
table.translatesAutoresizingMaskIntoConstraints = false
titleField.placeholderString = "Movie Title"
directorField.placeholderString = "Name of Director"
releaseYearField.placeholderString = "Year Released"
// Configure table
table.addTableColumn(titleColumn)
table.addTableColumn(directorColumn)
table.addTableColumn(releaseYearColumn)
titleColumn.title = "Movie Title"
directorColumn.title = "Director"
releaseYearColumn.title = "Year Released"
table.delegate = self
table.dataSource = self
table.focusRingType = .none
// Configure scroll view
scrollView.documentView = table
scrollView.hasVerticalScroller = true
scrollView.hasHorizontalScroller = false
scrollView.autohidesScrollers = true
// Add views to the hierarchy
view.addSubview(titleField)
view.addSubview(directorField)
view.addSubview(releaseYearField)
view.addSubview(addMovieButton)
view.addSubview(scrollView)
// view.addSubview(table)
NSLayoutConstraint.activate([
titleField.widthAnchor.constraint(equalToConstant: 150),
directorField.widthAnchor.constraint(equalToConstant: 150),
releaseYearField.widthAnchor.constraint(equalToConstant: 150),
addMovieButton.widthAnchor.constraint(equalToConstant: 100),
titleField.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
directorField.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
releaseYearField.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
addMovieButton.topAnchor.constraint(equalTo: view.topAnchor, constant: 20),
directorField.trailingAnchor.constraint(equalTo: view.centerXAnchor, constant: -10),
releaseYearField.leadingAnchor.constraint(equalTo: view.centerXAnchor, constant: 10),
titleField.trailingAnchor.constraint(equalTo: directorField.leadingAnchor, constant: -20),
addMovieButton.leadingAnchor.constraint(equalTo: releaseYearField.trailingAnchor, constant: 20),
scrollView.topAnchor.constraint(equalTo: titleField.bottomAnchor, constant: 20),
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -20),
// table.widthAnchor.constraint(equalTo: scrollView.contentView.widthAnchor),
// table.heightAnchor.constraint(equalTo: scrollView.contentView.heightAnchor)
])
}
@objc private func addMovie() {
let title = titleField.stringValue
let director = directorField.stringValue
guard let year = Int(releaseYearField.stringValue) else {
return
}
let movie = Movie(title: title, director: director, releaseYear: year)
data.append(movie)
// Approach 1
// table.reloadData()
// Approach 2
let newRow = data.count - 1
table.insertRows(at: IndexSet(integer: newRow), withAnimation: .slideDown)
}
Using table.reloadData works fine and I can scroll to the new rows as I add them, but it doesn't have the smooth animation I want to achieve.
What is the best practice and correct way for using table.insertRows to avoid this issue?
Note: I am using programmatic constraints only.