创建UINavigationBarButton中的缩进外观 - 以编程方式

时间:2012-06-12 08:55:04

标签: ios cocoa-touch uiview core-graphics quartz-graphics

我正在尝试以编程方式重新创建可在UINavigationBarButton上看到的缩进按钮外观。不是闪亮的双色调外观或渐变,只是周边阴影:

enter image description here

它看起来像整个视野周围的内部暗影,顶部稍暗一些?然后在下视图周边外部突出阴影。

我在Core Graphics上玩了一些,并尝试使用QuartzCore并使用view.layer.shadowRadius和.shadowOffset进行阴影处理,但是甚至无法让低调突出显示正确。我也不知道从哪里开始实现内部偏移的暗阴影和外部偏移的光阴影。

2 个答案:

答案 0 :(得分:5)

好像你想要一个看起来像阴影的边框。由于阴影看起来像是某种渐变,因此乍看之下将边框设置为渐变是不可能的。但是,可以创建表示边框的路径,然后使用渐变填充该路径。 Apple提供了一个名为CGPathCreateCopyByStrokingPath的鲜为人知的函数。这将采用一条路径(例如,圆角矩形),并根据您传递给函数的设置创建一条新路径,该路径将是旧路径的笔划(如线宽,连接/上限设置,斜接限制等) )。所以我们假设您定义了一条路径(这不是Apple提供的路径,但它的相似之处):

+ (UIBezierPath *) bezierPathForBackButtonInRect:(CGRect)rect withRoundingRadius:(CGFloat)radius{
    UIBezierPath *path = [UIBezierPath bezierPath];
    CGPoint mPoint = CGPointMake(CGRectGetMaxX(rect) - radius, rect.origin.y);
    CGPoint ctrlPoint = mPoint;
    [path moveToPoint:mPoint];

    ctrlPoint.y += radius;
    mPoint.x += radius;
    mPoint.y += radius;
    if (radius > 0) [path addArcWithCenter:ctrlPoint radius:radius startAngle:M_PI + M_PI_2 endAngle:0 clockwise:YES];

    mPoint.y = CGRectGetMaxY(rect) - radius;
    [path addLineToPoint:mPoint];

    ctrlPoint = mPoint;
    mPoint.y += radius;
    mPoint.x -= radius;
    ctrlPoint.x -= radius;
    if (radius > 0) [path addArcWithCenter:ctrlPoint radius:radius startAngle:0 endAngle:M_PI_2 clockwise:YES];

    mPoint.x = rect.origin.x + (10.0f);
    [path addLineToPoint:mPoint];

    [path addLineToPoint:CGPointMake(rect.origin.x, CGRectGetMidY(rect))];

    mPoint.y = rect.origin.y;
    [path addLineToPoint:mPoint];

    [path closePath];
    return path;
}

这会返回类似于Apple的后退按钮的路径(我在我的应用中使用此功能)。我已将此方法(以及其他数十种方法)添加为UIBezierPath的类别。

现在让我们在绘图例程中添加内部阴影:

- (void) drawRect:(CGRect)rect{
    UIBezierPath *path = [UIBezierPath bezierPathForBackButtonInRect:rect withRoundingRadius:5.0f];
    //Just fill with blue color, do what you want here for the button
    [[UIColor blueColor] setFill]; 
    [path fill];

    [path addClip]; //Not completely necessary, but borders are actually drawn 'around' the path edge, so that half is inside your path, half is outside adding this will ensure the shadow only fills inside the path

    //This strokes the standard path, however you might want to might want to  inset the rect, create a new 'back button path' off the inset rect and create the inner shadow path off that.  
    //The line width of 2.0f will actually show up as 1.0f with the above clip: [path addClip];, due to the fact that borders are drawn around the edge 
    UIBezierPath *innerShadow = [UIBezierPath bezierPathWithCGPath: CGPathCreateCopyByStrokingPath(path.CGPath, NULL, 2.0f, path.lineCapStyle, path.lineJoinStyle, path.miterLimit)];
    //You need this, otherwise the center (inside your path) will also be filled with the gradient, which you don't want
    innerShadow.usesEvenOddFillRule = YES;
    [innerShadow addClip];

    //Now lets fill it with a vertical gradient
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGPoint start = CGPointMake(0, 0);
    CGPoint end = CGPointMake(0, CGRectGetMaxY(rect));
    CGFloat locations[2] = { 0.0f, 1.0f};
    NSArray *colors =  [NSArray arrayWithObjects:(id)[UIColor colorWithWhite:.7f alpha:.5f].CGColor, (id)[UIColor colorWithWhite:.3f alpha:.5f].CGColor, nil];
    CGGradientRef gradRef = CGGradientCreateWithColors(CGColorSpaceCreateDeviceRGB(), (__bridge CFArrayRef)colors, locations);
    CGContextDrawLinearGradient(context, gradRef, start, end, 0);
    CGGradientRelease(gradRef);
}

现在这只是一个简单的例子。我不会保存/恢复您可能想要做的事情或任何事情。有些事情你可能仍然想做些让它变得更好,比如可能会插入阴影'路径如果要使用普通边框。您可能想要使用更多/不同的颜色和位置。但这应该让你开始。

<强> 更新

您可以使用另一种方法来创建此效果。我写了一个算法来在核心图形中斜切任意bezier路径。这可用于创建您正在寻找的效果。这是我在我的应用中使用它的示例:

Bevelled Back Button

您将CGContextRef,CGPathRef,斜角的大小以及您希望它用于高光/阴影的颜色传递给例程。

我在此处使用的代码可以在这里找到:Github - Beveling Algorithm

我还在这里解释代码和方法:Beveling-Shapes in Core Graphics

答案 1 :(得分:2)

使用图层的阴影不会这样做。你需要一个轻的外阴影和一个黑暗的内阴影来获得这种效果。图层只能有一个(外部)阴影。 (此外,图层阴影会动态重绘,并强制执行基于CPU的渲染,从而导致性能下降。)

您需要使用CoreGraphics在视图的drawRect:方法或图层的drawInContext:方法中创建自己的绘图。 (或者您绘制到图像上下文然后重复使用图像。)所述绘图主要使用CGContext functions。 (我在下面列举一些,但是这个链接都有他们的文档。)

对于圆形矩形按钮,您可能会发现创建适当的CGPath很繁琐 - 相反,您可以使用+[UIBezierPath bezierPathWithRoundedRect:cornerRadius:]然后使用路径的CGPath属性来设置上下文的当前路径{{ 1}}。

您可以通过将剪切路径(请参阅CGContextAddPath和相关函数)设置为按钮的形状,设置阴影(请参阅CGContextClip和相关函数),然后创建内部阴影,然后在想要遮蔽的形状外面画画。对于内部阴影,笔划(CGContextSetShadowWithColor)一个比你的按钮稍大的圆形矩形,使用粗笔划宽度(CGContextStrokePath)所以有足够的“墨水”来生成阴影(请记住,由于剪切路径,此笔划将不可见。

你可以用同样的方式创建一个外部阴影 - 这次不要使用剪切路径,因为你希望阴影在形状之外,并填充(CGContextSetLineWidth)你的形状按钮而不是抚摸它。请注意,绘制阴影是一种“模式”:保存图形状态(CGContextFillPath),设置阴影,然后绘制想要看到阴影的形状(当不显示形状本身时)你处于这种模式),最后恢复状态(CGContextSaveGState)以摆脱“阴影模式”。由于该模式不绘制形状,只绘制阴影,因此您需要单独绘制形状本身。

也有订单要做到这一点。如果你考虑用物理媒体绘制这些东西的顺序,应该是显而易见的:首先绘制外部阴影,然后绘制按钮的填充,然后绘制内部阴影。如果内部阴影没有给出足够明显的轮廓,则可以在此之后添加笔划。


有一些绘图工具可以输出CoreGraphics的源代码:Opacity是我使用的。但要小心这些,因为它们生成的代码可能效率不高。