我正在使用此代码来整理UIView
的一角:
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.view.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.view.bounds;
maskLayer.path = maskPath.CGPath;
self.view.layer.mask = maskLayer;
self.view.layer.masksToBounds = NO;
只要我没有调整视图大小,此代码就可以工作。如果我使视图变大,则不会出现新区域,因为它超出了遮罩层的边界(此遮罩层不会自动调整视图的大小)。我可以让面具尽可能大,但它可以在iPad上全屏显示,所以我担心面具的性能很大(我的面具中不止一个) UI)。此外,超大尺寸的蒙版不适用于需要将上方右侧角(单独)四舍五入的情况。
有没有更简单,更简单的方法来实现这一目标?
更新:这就是我想要实现的目标:http://i.imgur.com/W2AfRBd.png(我想要的圆角以绿色圈出来)。
我已经实现了这个的工作版本,使用UINavigationController
的子类并覆盖viewDidLayoutSubviews
,如下所示:
- (void)viewDidLayoutSubviews {
CGRect rect = self.view.bounds;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect
byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(8.0, 8.0)];
self.maskLayer = [CAShapeLayer layer];
self.maskLayer.frame = rect;
self.maskLayer.path = maskPath.CGPath;
self.view.layer.mask = self.maskLayer;
}
然后我用我的视图控制器实例化我的UINavigationController
子类,然后我将导航控制器视图的帧偏移20px(y)以显示状态栏并留下44像素高的导航栏,如如图所示。
代码正在运行,但它根本不能很好地处理旋转。当应用程序轮换时,viewDidLayoutSubviews
在旋转之前被调用,我的代码会在旋转后创建一个适合视图的蒙版;这会对旋转产生不希望的阻塞,其中应该隐藏的位在旋转期间暴露。此外,虽然应用程序的旋转在没有此蒙版的情况下非常平滑,但是在创建蒙版的情况下,旋转变得明显不稳定且变慢。
iPad应用程序Evomail也有这样的圆角,他们的应用程序也遇到了同样的问题。
答案 0 :(得分:5)
问题是,CoreAnimation属性不会在UIKit动画块中设置动画。您需要创建一个单独的动画,该动画与UIKit动画具有相同的曲线和持续时间。
我在viewDidLoad
中创建了遮罩层。当视图即将布局时,我只修改遮罩层的path
属性。
您不知道布局回调方法中的旋转持续时间,但您在旋转之前(以及触发布局之前)确实知道它,因此您可以将其保留在那里。
以下代码效果很好。
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
//Keep duration for next layout.
_duration = duration;
}
-(void)viewWillLayoutSubviews
{
[super viewWillLayoutSubviews];
UIBezierPath* maskPath = [UIBezierPath bezierPathWithRoundedRect:self.view.bounds byRoundingCorners:UIRectCornerTopLeft cornerRadii:CGSizeMake(10, 10)];
CABasicAnimation* animation;
if(_duration > 0)
{
animation = [CABasicAnimation animationWithKeyPath:@"path"];
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[animation setDuration:_duration];
//Set old value
[animation setFromValue:(id)((CAShapeLayer*)self.view.layer.mask).path];
//Set new value
[animation setToValue:(id)maskPath.CGPath];
}
((CAShapeLayer*)self.view.layer.mask).path = maskPath.CGPath;
if(_duration > 0)
{
[self.view.layer.mask addAnimation:animation forKey:@"path"];
}
//Zero duration for next layout.
_duration = 0;
}
答案 1 :(得分:2)
我知道这是一种非常黑客的做法,但你不能只是在角落顶部添加一个png吗?
丑陋我知道,但它不会影响性能,如果它是子视图并且用户不会注意到,旋转将会很好。
答案 2 :(得分:1)
两个想法:
调整视图大小时调整蒙版大小。您没有按照自动调整子视图大小的方式调整子层的自动大小,但您仍然可以获得一个事件,因此您可以手动调整子图层的大小。
或者...如果这是一个您负责绘制和显示的视图,请将角落的圆角作为首先绘制视图的一部分(通过剪裁)。这实际上是最有效的方法。
答案 3 :(得分:1)
您可以继承您正在使用的视图并覆盖“layoutSubviews”方法。每当您的视图尺寸发生变化时,都会调用此文件。
即使“self.view”(在您的代码中引用)是您的viewcontroller视图,您仍然可以将此视图设置为storyboard中的自定义类。这是子类的修改代码:
- (void)layoutSubviews {
[super layoutSubviews];
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.layer.masksToBounds = NO;
}
答案 4 :(得分:1)
我认为你应该创建一个自定义视图,在需要的任何时候自动更新,这意味着任何时候调用setNeedsDisplay。
我建议创建一个自定义的UIView子类,如下所示:
// OneRoundedCornerUIView.h
#import <UIKit/UIKit.h>
@interface OneRoundedCornerUIView : UIView //Subclass of UIView
@end
// OneRoundedCornerUIView.m
#import "OneRoundedCornerUIView.h"
@implementation OneRoundedCornerUIView
- (void) setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsDisplay];
}
// Override drawRect as follows.
- (void)drawRect:(CGRect)rect
{
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.bounds byRoundingCorners:(UIRectCornerTopLeft) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.layer.masksToBounds = NO;
}
@end
完成此操作后,您只需将视图设置为OneRoundedCornerUIView实例而不是UIView实例,每次调整大小或更改其框架时,视图都会平滑更新。我刚做了一些测试,看起来效果很好。
此解决方案也可以轻松自定义,以便有一个视图,您可以轻松地设置哪些角应该在哪个角上以及哪些角不应来自View Controller。实施如下:
// OneRoundedCornerUIView.h
#import <UIKit/UIKit.h>
@interface OneRoundedCornerUIView : UIView //Subclass of UIView
// This properties are declared in the public API so that you can setup from your ViewController (it also works if you decide to add/remove corners at any time as the setter of each of these properties will call setNeedsDisplay - as shown in the implementation file)
@property (nonatomic, getter = isTopLeftCornerOn) BOOL topLeftCornerOn;
@property (nonatomic, getter = isTopRightCornerOn) BOOL topRightCornerOn;
@property (nonatomic, getter = isBottomLeftCornerOn) BOOL bottomLeftCornerOn;
@property (nonatomic, getter = isBottomRightCornerOn) BOOL bottomRightCornerOn;
@end
// OneRoundedCornerUIView.m
#import "OneRoundedCornerUIView.h"
@implementation OneRoundedCornerUIView
- (void) setFrame:(CGRect)frame
{
[super setFrame:frame];
[self setNeedsDisplay];
}
- (void) setTopLeftCornerOn:(BOOL)topLeftCornerOn
{
_topLeftCornerOn = topLeftCornerOn;
[self setNeedsDisplay];
}
- (void) setTopRightCornerOn:(BOOL)topRightCornerOn
{
_topRightCornerOn = topRightCornerOn;
[self setNeedsDisplay];
}
-(void) setBottomLeftCornerOn:(BOOL)bottomLeftCornerOn
{
_bottomLeftCornerOn = bottomLeftCornerOn;
[self setNeedsDisplay];
}
-(void) setBottomRightCornerOn:(BOOL)bottomRightCornerOn
{
_bottomRightCornerOn = bottomRightCornerOn;
[self setNeedsDisplay];
}
// Override drawRect as follows.
- (void)drawRect:(CGRect)rect
{
UIRectCorner topLeftCorner = 0;
UIRectCorner topRightCorner = 0;
UIRectCorner bottomLeftCorner = 0;
UIRectCorner bottomRightCorner = 0;
if (self.isTopLeftCornerOn) topLeftCorner = UIRectCornerTopLeft;
if (self.isTopRightCornerOn) topRightCorner = UIRectCornerTopRight;
if (self.isBottomLeftCornerOn) bottomLeftCorner = UIRectCornerBottomLeft;
if (self.isBottomRightCornerOn) bottomRightCorner = UIRectCornerBottomRight;
UIRectCorner corners = topLeftCorner | topRightCorner | bottomLeftCorner | bottomRightCorner;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:
self.bounds byRoundingCorners:(corners) cornerRadii:
CGSizeMake(10.0, 10.0)];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
self.layer.masksToBounds = NO;
}
@end
答案 5 :(得分:1)
我喜欢做@Martin建议的事情。只要圆角后面没有动画内容,您就可以将其关闭 - 即使在最前面的视图后面显示需要圆角的位图图像。
我创建了一个模拟截图的示例项目。神奇发生在名为UIView
的{{1}}子类中。您可以将此视图放置在任何您想要的位置 - 在要在其上显示圆角的视图上方,设置属性以说明要舍入的角(通过调整视图的大小来调整半径),以及设置属性是你希望在角落里看到的“背景视图”。
以下是样本的回购:https://github.com/TomSwift/testRoundedCorner
这是TSRoundedCornerView
的绘画魔力。基本上我们用圆角创建一个倒置的剪辑路径,然后绘制背景。
TSRoundedCornerView
答案 6 :(得分:0)
我再次考虑过这个问题,我认为有一个更简单的解决方案。我更新了我的样本以展示两种解决方案。
新解决方案是简单地创建一个具有4个圆角的容器视图(通过CALayer cornerRadius
)。您可以调整该视图的大小,以便在屏幕上只显示您感兴趣的角落。如果您需要圆角的三个角或两个相对的(在对角线上)圆角,此解决方案不能很好地工作。我认为它适用于大多数其他情况,包括您在问题和屏幕截图中描述的情况。
答案 7 :(得分:0)
试试这个。希望这会对你有所帮助。
UIView* parent = [[UIView alloc] initWithFrame:CGRectMake(10,10,100,100)];
parent.clipsToBounds = YES;
UIView* child = [[UIView alloc] new];
child.clipsToBounds = YES;
child.layer.cornerRadius = 3.0f;
child.backgroundColor = [UIColor redColor];
child.frame = CGRectOffset(parent.bounds, +4, -4);
[parent addSubView:child];
答案 8 :(得分:0)
如果你想在Swift中这样做,我可以建议你使用UIView的扩展。通过这样做,所有子类都可以使用以下方法:
import QuartzCore
extension UIView {
func roundCorner(corners: UIRectCorner, radius: CGFloat) {
let maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: corners, cornerRadii: CGSizeMake(radius, radius))
var maskLayer = CAShapeLayer()
maskLayer.frame = self.bounds;
maskLayer.path = maskPath.CGPath;
self.layer.mask = maskLayer;
}
}
self.anImageView.roundCorner(UIRectCorner.TopRight, radius: 10)