如何在IOS中完成可调整大小的UIView的一角?

时间:2014-01-23 02:45:11

标签: ios calayer

我正在使用此代码来整理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也有这样的圆角,他们的应用程序也遇到了同样的问题。

9 个答案:

答案 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)。您可以调整该视图的大小,以便在屏幕上只显示您感兴趣的角落。如果您需要圆角的三个角或两个相对的(在对角线上)圆角,此解决方案不能很好地工作。我认为它适用于大多数其他情况,包括您在问题和屏幕截图中描述的情况。

以下是样本的回购:https://github.com/TomSwift/testRoundedCorner

答案 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)