Set NSTextField frame height in tableView viewForTableColumn not working - macos

I have a .xib with 2-3 NSTextFields displayed in it (as shown below). I would like to resize one of these NSTextFields so it best fits the content it is displaying. My problem is that I cannot change the size of the Text Field in the viewForTableColumn at all.
I have successfully set my heightOfRow. That is working nicely.
I have turned off AutoLayout for the field as it wasn't quite working properly for me.
Now in tableView viewForTableColumn I have the following code:
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView?
{
let cell = tableView.makeViewWithIdentifier("MessageView", owner: self) as! MessageView!
var newFrame = cell.messageSubject.frame
newFrame.size.height = 200
cell.messageSubject.frame = newFrame
return cell
}
As you can see the frame for the Subject field remains unaltered.
Interestingly the size of the frame is always the default size set in the Interface builder even when AutoLayout is turned on.
My conclusion is that I'm trying to do this in the wrong method.
I have been able to resize the frame in a simple non-table based viewController with no problem.

Related

didSelectItemAt indexPath not fully called until second click

So it seems like some things in the code for "didselectitem" gets called on the first selection of a cell, but others don't update until the second click.
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let cellSize = collectionView.cellForItem(at: indexPath)?.frame.size
popup.frame.size = cellSize!
popupWordLabel.text = itemsArray[indexPath.row].word
popupNumberLabel.text = itemsArray[indexPath.row].number
popupDescriptionLabel.text = itemsArray[indexPath.row].descriptor
popupCenterConstraint.constant = 0
print("\(indexPath)")
I'm messing around to see what actually works, setting the popupCenterconstraint works on the first click, as does printing the indexPath. However updating the popup framesize doesn't update unless I click the cell a second time. This same thing happens with trying to animate the popup frame with cgaffinetransform, etc.
popup is a UIView that starts with the center constraint set out of screen.

In a view-based NSTableView, the NSTextField is clipped by an NSImageView

TL;DR:
In macOS 10.11, a view based NSTableView containing an NSTextField and an NSImageView right under the textfield, with some rows having an image and others not, some texts are clipped after having scrolled the table down then up.
Before scrolling:
After scrolling:
When I say "clipped" it means that the text view is at its minimum height as defined with autolayout, when it should be expanded instead.
Note that this faulty behavior only happens in macOS 10.11 Mavericks. There's no such issues with macOS 10.12 Sierra.
Context
macOS 10.11+, view based NSTableView.
Each row has a textfield, and an image view just below the textfield.
All elements have autolayout constraints set in IB.
Goal
The text view has to adapt its height vertically - the image view should move accordingly, staying glued to the bottom of the textfield.
Of course the row itself also has to adapt its height.
Sometimes there's no image to display, in this case the image view should not be visible.
This is how it's supposed to render when it works properly:
Current implementation
The row is a subclass of NSTableCellView.
In the cell the textfield is set with an attributed string.
In tableView(_ tableView: NSTableView, heightOfRow row: Int) -> CGFloat, I make a dummy textView that I use to find the actual textfield's height, and return an amended row height accordingly. I also check if there's an image to display and if it's the case I add the image height to the row height.
let ns = NSTextField(frame: NSRect(x: 0, y: 0, width: defaultWidth, height: 0))
ns.attributedStringValue = // the attributed string
var h = ns.attributedStringValue.boundingRect(with: ns.bounds.size, options: [.usesLineFragmentOrigin, .usesFontLeading]).height
if post.hasImage {
h += image.height
}
return h + margins
In tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? I prepare the actual cell contents:
getUserAvatar { img in
DispatchQueue.main.async {
cell.iconBackground.layer?.backgroundColor = self.prefs.colors.iconBackground.cgColor
cell.iconBackground.rounded(amount: 6)
cell.iconView.rounded(amount: 6)
cell.iconView.image = img
}
}
cell.usernameLabel.attributedStringValue = xxx
cell.dateLabel.attributedStringValue = xxx
// the textView at the top of the cell
cell.textLabel.attributedStringValue = xxx
// the imageView right under the textView
if post.hasImage {
getImage { img in
DispatchQueue.main.async {
cell.postImage.image = img
}
}
}
Issues
When scrolling the tableView, there's display issues as soon as one or several rows have an image in the image view.
Clipped text
Sometimes the text is clipped, probably because the empty image view is masking the bottom part of the text:
Normal
Clipped
IMPORTANT: resizing the table triggers a redraw and fixes the display issue...
What I've tried
Cell reuse
I thought the main issue was because of cell reuse by the tableView.
So I'm hiding the image field by default in tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView?, only unhiding if there's an image to set, and I do it on the main thread:
guard let cell = tableView.make(withIdentifier: "xxx", owner: self) as? PostTableCellView else {
return nil
}
DispatchQueue.main.async {
cell.postImage.isHidden = true
cell.postImage.image = nil
}
// set the text view here, the buttons, labels, etc, then this async part runs:
downloadImage { img in
DispatchQueue.main.async {
cell.postImage.isHidden = false
cell.postImage.image = img
}
}
// more setup
return cell
But the imageView is still blocking the text view in some cases.
And anyway, sometimes the text is clipped at the first display, before any scrolling is done...
CoreAnimation Layer
I thought maybe the cells need to be layer backed so that they're correctly redisplayed, so I've enabled CoreAnimation layer on the scrollView, the tableView, the tableCell, etc. But I hardly see any difference.
A/B test
If I remove the imageView completely and only deal with the textfield everything works ok. Having the imageView is definitely what is causing issues here.
Autolayout
I've tried to handle the display of the row contents without using autolayout but to no avail. I've also tried to set different constraints but clearly I suck at autolayout and didn't manage to find a good alternative to my current constraints.
Alternative: NSAttributedString with image
I've tried to remove the image view and have the image added at the end of the attributed string in the text view instead. But the result is often ugly when it works - and for most times it just doesn't work (for example when the image can't be downloaded in time to be added to the attributed string, leaving the cell without text or image at all and at a wrong height).
Question
What am I doing wrong? How could I fix these issues? My guess is that I should change the autolayout constraints but I don't see a working solution.
Or maybe would you do this entirely differently?
Text field has "Preferred Width" field in IB which allows tune up correlation of intrinsic size with auto-layout calculated size.
Changing this IB property value to "First Runtime Layout Width" or "Explicit" often helps resolving similar issues.
This resolved a lot of my issues in past.

Pre-selecting row in NSTableView not working

I am using the code below in viewDidLoad() to try and pre-select a table row in a tableView object (tblProjectNumber). No errors at compile or run time.
Everything loads ok and I can select table rows by a mouse click without any problems once the view is loaded. However, I cannot load the view with the required default row pre-selected. I have tried the following code in viewDidLoad() (the last three lines refer). The tableView does not show a selected row and does not scroll the row at index(100) when the data loads.
override func viewDidLoad() {
super.viewDidLoad()
self.view.wantsLayer = true
self.view.layer?.backgroundColor = NSColor.windowBackgroundColor().CGColor
self.view.window?.title = "Project Detail Module"
self.view.window?.titleVisibility
// sort the list of project numbers so that they display correctly when the view is first loaded
self.sortProjectNumberArray()
self.loadProjectRowDataArray()
self.sortPPRFilesForLatestDate()
// pre-select table row to load on initial display of the tableView
let index = NSIndexSet(index: 100)
self.tblProjectNumber.scrollRowToVisible(100)
self.tblProjectNumber.selectRowIndexes(index, byExtendingSelection: true)
}
The answeres here:
Select row in tableView programmatically by other method beside calling their indexPath
Select tableview row programmatically
Xcode - Swift Programmatically select tablecell
have helped but I haven't been able to solve the issue. Also the call to selectRowAtIndexPath used in the last link doesn't seem to be available in Swift so it doesn't work for me either.
Try your code inside tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? method.
Don't know which select method will work in OSX but selectRowAtIndexPath is the correct method for iOS. So try every possible selectIndex method of OSX but inside the viewForTableColumn method through which the tabledata is actully being displayed. i.e. while displaying tabledata..it will check for the index you want to be selected.
Hope this may help !

Xcode custom UITableViewCell has unwanted image indentation after selection

Using Xcode 6.3.1 and iOS 8.3 (and Swift 1.2 for what it matters here) I created a custom UITableViewCell that includes an UIImageView, and two UILabels. The whole thing has been formatted using Auto-Layout. No errors, no warnings. When I run the app, all goes fine until... I selected a table row. In that case the image indents a little (simply selection, no editing enabled) and remains indented.
See the first row of the image to get an impression:
Hiding the image removes the problem, and setting the UITableViewCell's Selection parameter (in Xcode) to None also removes the problem (but that feels unnatural when selecting a row).
I am not sure whether it is Auto Layout related as it only appears after selecting the row, but I do not know how to fix it properly. Any ideas?
Edit: here is my cell code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(customCellIdentifier, forIndexPath: indexPath) as! MenuTableViewCell
let item = menuItems[indexPath.row]
cell.titleLabel!.text = item.title
cell.subtitleLabel?.text = item.subTitle
cell.imageView?.image = UIImage(named: item.icon)
return cell
}

Why does makeViewWithIdentifier:owner: return different types sporadically?

Hello and thanks for reading. I am trying to populate a view based NSTableView from an array of People objects. In my setup i'm using Storyboards with two xib files (one for the main tableview and the other for my custom view).
The call to makeViewWithIdentifier:owner: within the delegate method "tableView viewForTableColumn row" is returning different types for no obvious reason. Sometimes when i compile it returns objects of type "MyOView" (see Console Output 1) which is my custom view class and yet other times when i compile (despite literally no change in my code) it returns NSTextField (see Console Output 2)
Why is this happening?
Console Output 1:
class name of cell is: Saddle.MyOView
class name of cell is: Saddle.MyOView
Console Output 2:
Could not cast value of type 'NSTextField' (0x7fff7e1bbf40) to 'Saddle.MyOView' (0x100017980).
(lldb)
Here is my implementation of tableView viewForTableColumn Row...
func tableView(tableView: NSTableView, viewForTableColumn tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cell = tableView.makeViewWithIdentifier("MyOView", owner: self) as! MyOView
println("class name of cell is: " + cell.className)
let person : Person = allPeopleInRace[row]
cell.itemNumber.stringValue = person.number.description
cell.itemName.stringValue = person.name
return cell
}
In the ViewController in viewDidLoad() i have registered the second nib
let nib = NSNib(nibNamed: "MyOView", bundle: NSBundle.mainBundle())
mainTableView.registerNib(nib!, forIdentifier: "MyOView")
Any help would be appreciated :)
thanks
After some more digging around I realised that I hadn't named my custom cell Xib. Hope this is helpful to someone else.

Resources