I am using a SplitViewController; The master is tableView with a series of RightDetail cells whose Attributed Strings are set via a callback from a value setting popover.
The value passed back and set via a method that sets the detailTextLabel of the cell. It does this within a DispatchQueue.main.async as some of updates to detailTextLabel are made via calls from Network.Framework
The strange thing is when the popover makes the changes the change to the attributed string is not presented correctly. The focus has to change to the next table cell before the correct drawing occurs.
So it is only when focus changes to "Flow Rate" that temperature is rendered correctly, as shown blow.
I have stepped through the following code and checked that the correct value is being written to self.buttonCell?.detailTextLabel?.attributedText
You'll also see that I've added setNeedsLayout() and setNeedsFocusUpdate()
func setDetailValueWithUnit( to index: Int) {
/** sets the detailtext to the value as the specified index and appends units.
it does this within the main dispatchQueue as this method is called by
operationResult which is called by the statemachine.
**/
DispatchQueue.main.async {
let font = self.buttonCell?.detailTextLabel?.font
let finalString = NSMutableAttributedString(string: valueString,attributes: [.font:font!])
if let units = self.units() {
finalString.append(units)
self.buttonCell?.detailTextLabel?.attributedText = finalString
//next 2 lines experimental in an attempt to fix the attributed text not
//being consistently redendered correctly
self.buttonCell?.detailTextLabel?.setNeedsLayout()
self.buttonCell?.detailTextLabel?.setNeedsFocusUpdate()
NSLog("setDetailValueWithUnit: %d", index)
return
} else {
assert(false, "No Units found")
}
}
}
The problem is that what you are doing is completely wrong. You should never be touching a cell's text label font, text, or anything else directly like that. That's not how a table view works. You should set the table's data model and update the table from there, thus causing cellForRowAt: to be called for the visible cells. In that way, only cellForRowAt: configures the cell, and all will be well.
When I click with the mouse I can get the cell position with myTableView.clickedRow and myTableView.clickedColumn.
However if I click on the first cell and then I press "Tab" key on my keyboard, the selection will stay on row = 0 and column = 0.
Is there a way to track the row and column index when changing cell with keyboard ?
When a text field in a cell is focused then the first responder is the field editor of the text field. Use row(for:) and column(for:) of NSTableView to get the row and column.
if let view = window.firstResponder as? NSView {
let column = tableView.column(for: view)
let row = tableView.row(for: view)
}
have a table view which consists of various different xib files for different question types. One such question is a multi option answer. For this row type, the row itself consists of another tableview each row has a label and switch for the number of options for that question and an overall switch above this inner table to mark this question as na. so When this na switch is tapped, I neeed to deselect all switches existing in the inner row. I have tried the following, but i have an issue if there are so many options that not all cells in the inner table are visible on screen:
#IBAction func switchChange(_ sender: UISwitch) {
let mcAnswerTable = sender.superview?.superview?.superview?.superview as! UITableView
for rows in mcAnswerTable.visibleCells {
if condition {
if let row = rows as? MCAnswerCell {
row.enabled.setOn(false, animated: true)
}
}
}
}
Basically avoid to manipulate the view (cell) directly unless you have no choice.
Here you have one. Create an appropriate model and use the controller to make the changes.
For the table view containing the switches use a custom class (let's call it Foo) with an isSelected property as data source model.
In cellForRow set the isOn property of the switch to the state of isSelected.
Subclass the table view cell and add a property of Foo and an IBAction for the switch.
In cellForRow pass the Foo instance to the custom cell to be able to update the isSelected property in the IBAction. Due to reference semantics the changes persist in the data source.
To deselect the switches call dataSourceArray.forEach {$0.isSelected = false} und tableView.reloadData()
Another huge benefit is that you have access to the state of all switches even of the cells which are not visible currently.
I have a NSTableView and want to track the position of its containing NSCells when the tableView got scrolled by the user.
I couldn’t find anything helpful. Would be great if someone can lead me into the right direction!
EDIT:
Thanks to #Ken Thomases and #Code Different, I just realized that I am using a view-based tableView, using tableView(_ tableView:viewFor tableColumn:row:), which returns a NSView.
However, that NSView is essentially a NSCell.
let cell = myTableView.make(withIdentifier: "customCell", owner: self) as! MyCustomTableCellView // NSTableCellView
So I really hope my initial question wasn’t misleading. I am still searching for a way how to track the position of the individual cells/views.
I set the behaviour of the NSScrollView (which contains the tableView) to Copy on Scroll in IB.
But when I check the x and y of the view/cells frame (within viewWillDraw of my MyCustomTableCellView subclass) it remains 0, 0.
NSScrollView doesn't use delegate. It uses the notification center to inform an observer that a change has taken place. The solution below assume vertical scrolling.
override func viewDidLoad() {
super.viewDidLoad()
// Observe the notification that the scroll view sends out whenever it finishes a scroll
let notificationName = NSNotification.Name.NSScrollViewDidLiveScroll
NotificationCenter.default.addObserver(self, selector: #selector(scrollViewDidScroll(_:)), name: notificationName, object: scrollView)
// Post an intial notification to so the user doesn't have to start scrolling to see the effect
scrollViewDidScroll(Notification(name: notificationName, object: scrollView, userInfo: nil))
}
// Whenever the scroll view finished scrolling, we will start coloring the rows
// based on how much they are visible in the scroll view. The idea is we will
// perform hit testing every n-pixel in the scroll view to see what table row
// lies there and change its color accordingly
func scrollViewDidScroll(_ notification: Notification) {
// The data's part of a table view begins with at the bottom of the table's header
let topEdge = tableView.headerView!.frame.height
let bottomEdge = scrollView.bounds.height
// We are going to do hit-testing every 10 pixel. For best efficiency, set
// the value to your typical row's height
let step = CGFloat(10.0)
for y in stride(from: topEdge, to: bottomEdge, by: step) {
let point = NSPoint(x: 10, y: y) // the point, in the coordinates of the scrollView
let hitPoint = scrollView.convert(point, to: tableView) // the same point, in the coordinates of the tableView
// The row that lies that the hitPoint
let row = tableView.row(at: hitPoint)
// If there is a row there
if row > -1 {
let rect = tableView.rect(ofRow: row) // the rect that contains row's view
let rowRect = tableView.convert(rect, to: scrollView) // the same rect, in the scrollView's coordinates system
let visibleRect = rowRect.intersection(scrollView.bounds) // the part of the row that visible from the scrollView
let visibility = visibleRect.height / rowRect.height // the percentage of the row that is visible
for column in 0..<tableView.numberOfColumns {
// Now iterate through every column in the row to change their color
if let cellView = tableView.view(atColumn: column, row: row, makeIfNecessary: true) as? NSTableCellView {
let color = cellView.textField?.textColor
// The rows in a typical text-only tableView is 17px tall
// It's hard to spot their grayness so we exaggerate the
// alpha component a bit here:
let alpha = visibility == 1 ? 1 : visibility / 3
cellView.textField?.textColor = color?.withAlphaComponent(alpha)
}
}
}
}
}
Result:
Update based on edited question:
First, just so you're aware, NSTableCellView is not an NSCell nor a subclass of it. When you are using a view-based table, you are not using NSCell for the cell views.
Also, a view's frame is always relative to the bounds of its immediate superview. It's not an absolute position. And the superview of the cell view is not the table view nor the scroll view. Cell views are inside of row views. That's why your cell view's origin is at 0, 0.
You could use NSTableView's frameOfCell(atColumn:row:) to determine where a given cell view is within the table view. I still don't think this is a good approach, though. Please see the last paragraph of my original answer, below:
Original answer:
Table views do not "contain" a bunch of NSCells as you seem to think. Also, NSCells do not have a position. The whole point of NSCell-based compound views is that they're much lighter-weight than an architecture that uses a separate object for each cell.
Usually, there's one NSCell for each table column. When the table view needs to draw the cells within a column, it configures that column's NSCell with the data for one cell and tells it to draw at that cell's position. Then, it configures that same NSCell with the data for the next cell and tells it to draw at the next position. Etc.
To do what you want, you could configure the scroll view to not copy on scroll. Then, the table view will be asked to draw everything whenever it is scrolled. Then, you would implement the tableView(_:willDisplayCell:for:row:) delegate method and apply the alpha value to the cells at the top and bottom edges of the scroll view.
But that's probably not a great approach.
I think you may have better luck by adding floating subviews to the scroll view that are partially transparent, with a gradient from fully opaque to fully transparent in the background color. So, instead of the cells fading out and letting the background show through, you put another view on top which only lets part of the cells show through.
I just solved the issue by myself.
Just set the contents view postsBoundsChangedNotifications to true and added an observer to NotificationCenter for NSViewBoundsDidChange. Works like a charm!
I've a horizontal stackview which has a stackview as a subview with 2 labels. Alignment & Distribution are set to Fill in Xcode Attribute Inspector. I want to display 7 subview stacks inside the stack view with 4 in first row and 3 in second row using only one horizontal stackview. I have loaded first row. When I add 5th element, it is appearing in 1st row instead of 2nd row. Number of subviews changes dynamically. If there are 3 elements it should only display only one row. If more than 5, it should display in 2 rows.Below is the code.
if let viewInfos = TestViewController.viewInfos{
let dataNib = UINib(nibName: "InfoStackView", bundle: frameworkBundle)
for viewInfo in viewInfos {
let infoStackView = dataNib.instantiateWithOwner(nil, options: nil)[0] as! InfoStackView
infoStackView.addInfoToStackView(viewInfo: viewInfo)
self.InfoStackView.addArrangedSubview(infoStackView)
}
}
Inside addInfoToStackView method, i add the label for each stackview
Above code adds stackview as subviews horizontally. I want to restrict 4 in first row and 3 in next row. Please help me how do this.