2

In my view-based NSTableView, each view has (among other stuff) an NSTextField, and an NSImageView right below the text field.

Sometimes, when I insert a new row at the top of the table, and this row has an image in its NSImageView, the text in the other rows becomes blurry/degraded.

The text becomes normal again after scrolling several times.

Not ok:

enter image description here

Ok:

enter image description here

An example where the text is only blurred in the rows after the one with the image:

enter image description here

This really makes me think it's a problem coming from .insertRows or noteHeightOfRows.

All elements have autolayout constraints set in IB.

The scroll view has a CoreAnimation layer set in IB. I also use the layer when preparing the cell:

cell.layer?.isOpaque = true
cell.textLabel.layer?.isOpaque = true

and sometimes

cell.layer?.borderColor = someColor
cell.layer?.borderWidth = someWidth

enter image description here

Rows are inserted with:

// update the model, then:
NSAnimationContext.runAnimationGroup({ (context) in
    context.allowsImplicitAnimation = true
    self.table.insertRows(at: index, withAnimation: [.effectGap])
})

Updating the row with the image:

// in tableViewCell, while populating the table
cell.postImage.image = img
cell.postImage.needsDisplay = true  

// once the image is downloaded
table.reloadData(forRowIndexes: ..., columnIndexes: ...)
table.noteHeightOfRows(withIndexesChanged: ...)

How to avoid this issue? It's hard to debug because it doesn't always happen and I can't see what are the reasons for it to happen when it does.

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
  • Hello, perhaps the image is pushing the text half pixel away from it's integral position - and those things are causing blurry texts. – Coldsteel48 Mar 03 '17 at 15:09
  • Oh, interesting idea. Maybe an issue with autolayout then? I'm not sure how I should debug this, though... – Eric Aya Mar 03 '17 at 15:11
  • You could just test in very easy manner - after the table view draw (layout subviews or whatever it called (sorry I am not an AutoLayout fan because of things like that)) just reset the frame of the textfield to integral frame - and force display... something like this `[myTetxField setFrame:NSIntegralRect(myTextField.frame)];` – Coldsteel48 Mar 03 '17 at 15:12
  • @ColdSteel I will try that, thanks a lot. // Oh by the way, Waves plugins are awesome. I use them everyday, thanks for the great work. ;) – Eric Aya Mar 03 '17 at 15:17
  • Thank you sir :-)! – Coldsteel48 Mar 03 '17 at 15:18
  • Well either there's something I don't understand or this trick doesn't work as we hoped. I suppose it's canceled by some call to needLayout or equivalent later in the chain of events. If I change the frame of the textField it's either ignored or it gives unexpected results. So I'm still in need of an answer - or an explanation of what happens, at least, if there's no solution and a refactor of my app is needed. – Eric Aya Mar 08 '17 at 16:40
  • Is there any layer in/on top of the textfield ? If on top please make sure that the option "can draw subviewa in to layer" something like this is checked. – Coldsteel48 Mar 08 '17 at 16:59
  • @ColdSteel Not from IB (in IB only the scroll view has one) but in code yes, I do this: `cell.canDrawSubviewsIntoLayer = false`, `cell.layer?.isOpaque = true`, `cell.textLabel.layer?.isOpaque = true`. The isOpaque properties are there because in a WWDC video they said it helped for performance - the canDrawSubviewsIntoLayer is there because... actually I don't remember why. Probably they talked about it in the video too. – Eric Aya Mar 08 '17 at 17:04
  • Ah! So, I have canDrawSubviewsIntoLayer but it is on the cell, not on the text field. Should I try on the text field too? – Eric Aya Mar 08 '17 at 17:05
  • No, on a cell should be enough (you can try for the sake of try, but I think it won't help). However in you code snippet it is set to false, try to set it to true. – Coldsteel48 Mar 08 '17 at 17:10
  • @ColdSteel Oh. So I did `cell.canDrawSubviewsIntoLayer = true` and the text has the blur *all the time* now. :) Does it talk to you? I'm lost. I understand it's progress in a way, though, to know that, but I can't draw any constructive conclusion. – Eric Aya Mar 08 '17 at 17:18
  • No, on a cell should be enough (you can try for the sake of try, but I think it won't help). However in you code snippet it is set to false, try to set it to true. - Yes opaque performs faster than transparent views - it avoids "pixel overdraw" (In simple english when it will draw it won't try again the views that are obscured by the opaque views). Can draw subviews in to a layer has some performance impacts (it will try to rasterize the subview and then draw as an image to the layer) but sometimes there is no choice and you have to draw them in to a layer – Coldsteel48 Mar 08 '17 at 17:22
  • In any case considering the fact that the text is drawn well when no image inserted - I would revert the candrawsubviews to false... It is hard to say, but I still think the problem is due to a not integral frames :-/. I suppose you did hook up the didLayoutSubviews of the tableview already ? – Coldsteel48 Mar 08 '17 at 17:31
  • "didLayoutSubviews" nope, I don't use that. Oh, maybe I could try your setFrame trick in it? – Eric Aya Mar 08 '17 at 17:33
  • Yes, I actually meant to use the setFrame there "after it layout subviews" , sorry it is hard - I am not on Mac in past couple of weeks so I cant tell the exact methods names, I just trying to help out of my head --- – Coldsteel48 Mar 08 '17 at 17:33
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/137578/discussion-between-eric-aya-and-coldsteel). – Eric Aya Mar 08 '17 at 17:39

1 Answers1

2

Due to the fact that the text got blurry only when an image was inserted, my first thought is that the NSTextField's frame is not "integral" (its pixel values are not integers). Which is a well known reason for getting blurry text.

You are using an Auto Layout feature of Cocoa so my suggestion is to override the void layout() method inside your view-based table view cell.

To call it's super method (to let Cocoa to do all the needed layout calculations) and just to set the frame of the textFiled to be the same frame, but integral.


Addendum: while the textField itself may have an integral frame - it still can be layed out on a "half pixel" (in real screen coordinates) which will still lead to the blurriness.

TextField is a subView of the cell so itss frame is not in the screen coordinates but in the cell coordinates.

Example: let's imagine that the cell has a frame of (0.5, 0.5 , 100, 100) which is not integral, and the textField has an integral frame of (10,10, 50, 50) which is integral by all means!

BUT when the textField will be layed out on the screen it will have the following frame: (10.5, 10.5, 50, 50) which is not integral - and will lead to drawing the textField on a "half pixel" (not integral) which leads to blurry text.

So in your case the cell itself must be layed out on an integral frame as well as the textField to ensure that the textField is on integral frame in screen coordinates.


In your tableViewCell subclass:

void layout()
{
    [super layout];

     //**** We have to ensure that the cell is also layed on an integral frame ****//

    [self setFrame:NSIntegralRect(self.frame)];

    //At this point after calling the [super layout]; myTextField will have a valid (autolayoutwise) frame so all you have to do is to ensure that it is indeed an integral frame doing so by calling NSIntegralRect(NSRect rect); method of Cocoa framework.
    [myTetxField setFrame:NSIntegralRect(myTextField.frame)];

    //Maybe optionally you will want to set everything to the integral rect :-)
    /*
    for(NSView * view in self.subViews)
    {
         [view setFrame:NSIntegralRect(view.frame)];
    }
    */
}
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Coldsteel48
  • 3,482
  • 4
  • 26
  • 43