NSScrollView: fade in a top-border like Messages.app

瘦欲@ 提交于 2020-01-03 01:48:41

问题


What I Want to Do:

In Messages.app on OS 10.10, when you scroll the left-most pane (the list of conversations) upwards, a nice horizontal line fades in over about 0.5 seconds. When you scroll back down, the line fades back out.


What I Have:

I am trying to achieve this effect in my own app and I've gotten very close. I subclassed NSScrollView and have done the following:

- (void) awakeFromNib
{
    _topBorderLayer = [[CALayer alloc] init];

    CGColorRef bgColor = CGColorCreateGenericGray(0.8, 1.0f);
    _topBorderLayer.backgroundColor = bgColor;
    CGColorRelease(bgColor);

    _topBorderLayer.frame = CGRectMake(0.0f, 0.0f, self.bounds.size.width, 1.0f);
    _topBorderLayer.autoresizingMask = kCALayerWidthSizable;
    _topBorderLayer.zPosition = 1000000000;

    _fadeInAnimation = [[CABasicAnimation animationWithKeyPath:@"opacity"] retain];
    _fadeInAnimation.duration = 0.6f;
    _fadeInAnimation.fromValue = @0;
    _fadeInAnimation.toValue = @1;
    _fadeInAnimation.removedOnCompletion = YES;
    _fadeInAnimation.fillMode = kCAFillModeBoth;

    [self.layer insertSublayer:_topBorderLayer atIndex:0];
}
- (void) layoutSublayersOfLayer:(CALayer *)layer
{
    NSPoint origin = [self.contentView documentVisibleRect].origin;

    // 10 is a fudge factor for blank space above first row's actual content
    if (origin.y > 10)
    {
        if (!_topBorderIsShowing)
        {
            _topBorderIsShowing = YES;
            [_topBorderLayer addAnimation:_fadeInAnimation forKey:nil];
            _topBorderLayer.opacity = 1.0f; 
        }
    }
    else
    {
        if (!_topBorderIsShowing)
        {
            _topBorderIsShowing = NO;
            // Fade out animation here; omitted for brevity
        }
    }
}

The Problem

The "border" sublayer that I add is not drawing over top of all other content in the ScrollView, so that we end up with this:

The frames around the image, textfield and checkbox in this row of my outlineView are "overdrawing" my border layer.

What Causes This

I THINK this is because the scrollView is contained inside an NSVisualEffectView that has Vibrancy enabled. The reason I think this is that if I change the color of my "border" sublayer to 100% black, this issue disappears. Likewise, if I turn on "Reduce Transparency" in OS X's System Preferences > Accessibility, the issue disappears.

I think the Vibrancy compositing is taking my grey border sublayer and the layers that represent each of those components in the outlineView row and mucking up the colors.

So... how do I stop that for a single layer? I've tried all sorts of things to overcome this. I feel like I'm 99% of the way to a solid implementation, but can't fix this last issue. Can anyone help?


NB:

I am aware that it's dangerous to muck directly with layers in a layer-backed environment. Apple's docs make it clear that we can't change certain properties of a view's layer if we're using layer-backing. However: adding and removing sublayers (as I am) is not a prohibited action.


回答1:


Update:

This answer, while it works, causes problems if you're using AutoLayout. You'll start to get warnings that the scrollView still needs update after calling Layout because something dirtied the layout in the middle of updating. I have not been able to find a workaround for that, yet.


Original solution:

Easiest way to fix the problem is just to inset the contentView by the height of the border sublayer with this:

- (void) tile
{
    id contentView = [self contentView];
    [super tile];
    [contentView setFrame:NSInsetRect([contentView frame], 0.0, 1.0)];
}

Should have thought of it hours ago. Works great. I'll leave the question for anyone who might be looking to implement these nice fading-borders.



来源:https://stackoverflow.com/questions/30526671/nsscrollview-fade-in-a-top-border-like-messages-app

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!