如何在IOS中的一个可resize的UIView的angular落?

我正在使用这个代码来完成我的UIView一个angular落:

 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)。 此外,一个超大的面具不适用于我需要右上angular(单独)四舍五入的情况。

有一个更简单,更简单的方法来实现这一点?

更新 :这是我想要实现的: http : //i.imgur.com/W2AfRBd.png (我想要的圆angular在绿色的圆圈内)。

我已经实现了这个工作版本,使用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在旋转之前被调用,我的代码创build一个适合旋转的视图的蒙版; 这产生了不希望的旋转阻塞,在旋转过程中应该隐藏的位被暴露。 另外,如果没有这个蒙版,应用程序的旋转是非常平滑的,而蒙版正在创build,旋转变得明显不稳定和缓慢。

iPad应用程序Evomail也有这样的圆angular,他们的应用程序遭受同样的问题。

我知道这是一个非常冒险的做法,但是你不能只是在angular落里添加一个PNG吗?

丑陋的我知道,但它不会影响性能,如果它的子视图和用户不会注意到旋转将是罚款。

问题是,CoreAnimation属性不会在UIKitanimation块中生成animation。 您需要创build一个单独的animation,它将具有与UIKitanimation相同的曲线和持续时间。

我在viewDidLoad创build了遮罩层。 当视图即将布局时,我只修改遮罩层的path属性。

您不知道布局callback方法中的旋转持续时间,但是在旋转之前(和布局被触发之前)您确实知道旋转持续时间,所以您可以将其保留在那里。

以下代码运行良好。

 - (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; } 

两个想法:

  • 调整视图大小时resize。 您不会像子视图自动resize那样自动调整子图层大小,但是您仍然会收到一个事件,因此您可以手动调整子图层的大小。

  • 或者…如果这是您负责绘制和显示的视图,则将angular的四舍五入作为您首先绘制视图的一部分(通过剪切)。 这实际上是最有效的方法。

你可以inheritance你正在使用的视图,并覆盖“layoutSubviews”方法。 每当您的视图尺寸发生变化时,都会调用它。

即使“self.view”(在您的代码中引用)是您的viewcontroller的视图,您仍然可以将此视图设置为故事板中的自定义类。 这里是子类的修改后的代码:

 - (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; } 

我认为你应该创build一个自定义的视图,在需要的时候自动更新,这意味着每当调用setNeedsDisplay的时候。

我build议是创build一个自定义的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实例,并且每当你resize或者改变框架时,你的视图都会被顺利地更新。 我刚刚做了一些testing,似乎完美的工作。

此解决scheme也可以很容易地定制,以便有一个视图,您可以轻松地设置哪些angular落应该是在哪些angular落不应该从您的视图控制器。 实施情况如下:

 // 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 

我是做马丁build议的粉丝。 只要圆angular后面没有animation内容,那么即使在最前面的视图后面显示的位图图像需要圆angular,也可以将其取消。

我创build了一个示例项目来模仿你的截图。 魔术发生在名为TSRoundedCornerViewUIView子类中。 您可以将此视图放置在您想要的任何位置 – 在要显示圆angular的视图上方,设置一个属性来指定圆angular(通过调整视图的大小来调整半径),以及设置属性“背景视图”,你想在angular落里可见。

这里是样本的回购: https : //github.com/TomSwift/testRoundedCorner

这是TSRoundedCornerView的绘图魔术。 基本上我们用我们的圆angular创build一个倒转的剪辑path,然后绘制背景。

 - (void) drawRect:(CGRect)rect { CGContextRef gc = UIGraphicsGetCurrentContext(); CGContextSaveGState(gc); { // create an inverted clip path // (thanks rob mayoff: http://stackoverflow.com/questions/9042725/drawrect-how-do-i-do-an-inverted-clip) UIBezierPath* bp = [UIBezierPath bezierPathWithRoundedRect: self.bounds byRoundingCorners: self.corner // eg UIRectCornerTopLeft cornerRadii: self.bounds.size]; CGContextAddPath(gc, bp.CGPath); CGContextAddRect(gc, CGRectInfinite); CGContextEOClip(gc); // self.backgroundView is the view we want to show peering out behind the rounded corner // this works well enough if there's only one layer to render and not a view hierarchy! [self.backgroundView.layer renderInContext: gc]; //$ the iOS7 way of rendering the contents of a view. It works, but only if the UIImageView has already painted... I think. //$ if you try this, be sure to setNeedsDisplay on this view from your view controller's viewDidAppear: method. // CGRect r = self.backgroundView.bounds; // r.origin = [self.backgroundView convertPoint: CGPointZero toView: self]; // [self.backgroundView drawViewHierarchyInRect: r // afterScreenUpdates: YES]; } CGContextRestoreGState(gc); } 

我想了一遍,我想有一个更简单的解决scheme。 我更新了我的示例以展示这两个解决scheme。

新的解决scheme是简单地创build一个具有4个圆angular的容器视图(通过CALayer cornerRadius )。 您可以调整该视图的大小,以便在屏幕上只显示您感兴趣的angular落。 如果您需要三个圆angular或两个相反(对angular线)圆angular,此解决scheme无法正常工作。 我认为它适用于大多数其他情况,包括您在问题和截图中描述的情况。

这里是样本的回购: https : //github.com/TomSwift/testRoundedCorner

尝试这个。 希望这会帮助你。

 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]; 

如果你想在Swift中做,我可以build议你使用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)