setNeedsDisplay看我就懂!

前言:

setNeedsDisplay异步执行的。它会自动调用drawRect方法,这样可以拿到 UIGraphicsGetCurrentContext,就可以绘制了。而setNeedsLayout会默认调用layoutSubViews,处理子视图中的一些数据。

一、着手

我定义了一个UIView的子类,用于演示使用setNeedsDisplay,这个CircleView子类会在draw(_ rect: CGRect)方法内简单绘制一个圆,它有一个颜色属性,这是我们将要设置用来改变圆的颜色。

import UIKit

class CircleView: UIView {
    var color:UIColor = UIColor.red
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath(ovalIn: rect)
        color.setFill()
        path.fill()
    }

}
复制代码

注意: setNeedsDisplayInRect相当于setNeedsDisplay,除了它是指定视图的特定矩形区域更新,而不是整个视图需要显示。

二、配置属性、组件

应用程序的下一部分是在故事板中配置一些UIKit组件,其中一个是CircleView

为了允许用户更改颜色,我已经定义了UIStepper控件,我还添加一个按钮,这将导致要使用的步进值来调整CircleView的颜色值。我不会详细介绍如何配置storyboard,因为重点是了解setNeedsDisplay

@IBOutlet weak var stepper: UIStepper! //change value,default value is 120.
@IBOutlet weak var circleView: CircleView!
复制代码

IBOutlets可以让我们访问circleViewStepper。对于步进值的变化,有IBActions,最后,有一个colorChangeBtn,它将调用一个未定义的方法changeColorFromStppers方法。该方法将收集步进器的值,使用它创建一个UIColor,并设置circleView的color属性。


    func changeColorFromStppers() {
        let valueFloat = CGFloat(stepper.value)
        
        let color = UIColor(red:valueFloat/255.0, green:valueFloat/255.0, blue:valueFloat/255.0, alpha:1.0)
        circleView.color = color
    }

复制代码

在viewDidLoad中,根据故事板中配置的步进器的默认值,我触发了一组初始的圆形颜色。该changeColorFromStppers方法创建CGFloat的用于步进数的值,创建的UIColor,然后设置circleView.color

    override func viewDidLoad() {
        super.viewDidLoad()

        self.changeColorFromStppers()
    }
复制代码
三、思考

现在更改stepper的值,然后点击colorChangeBtn按钮,发现圆形颜色没更新,这是什么原因呢?

一般来说,使用框架控件,当您设置属性(如显示标签或值)时,您将会使用该属性,这样会导致重新绘制控件,因为系统会实现对控件drawRect方法的调用。而我们自定义了自己的UIView子类,所以我们需要处理影响显示的控件的更新。在改变颜色的情况下,当然需要我们自己控制重新绘制。

根据上一篇文章setNeedsLayout和layoutIfNeeded看我就懂,所以我们在circleView.color = color之后添加了对setNeedsLayoutlayoutIfNeeded的调用,但结果同样不会更新。类似地,旋转设备也不会触发重新绘制圆形。这是因为视图的缓存机制,即便视图布局发生改变,也只是作为缓存。所以我们需要调用setNeedsDisplay,明确地告诉系统必须重新绘制,从而显示新的颜色 由此,我们需要考虑三个重要的原则:

1、在iOS中,视图很明显会被缓存。通常,给定的视图可能会被绘制一次,同时也不需要更新。 2、即使视图可能被移动或者有另一个视图重叠,也可能不需要重新绘制,因此您不能仅仅依靠已经移动整个视图或添加另一个视图基于setNeedsLayoutupdateIfNeeded来导致重绘 3、当编写重载drawRectUIView子类时,需要在需要重绘时指示给系统。因为drawRect不能被手动调用,所以您需要使用setNeedsDisplay方法告诉系统完成绘图,

四、添加setNeedsDisplay

所以接下来,我们需要添加setNeedsDisplay(),有两种方法

  • changeColorFromStppers添加 完整代码如下:
func changeColorFromStppers() {
    let valueFloat = CGFloat(stepper.value)
    
    let color = UIColor(red:valueFloat/255.0, green:100/255.0, blue:valueFloat/255.0, alpha:1.0)
    circleView.color = color
    //告诉系统需要重绘(Tell the system that circleView needs a redraw)
    circleView.setNeedsDisplay()
}
复制代码
  • CircleView使用didSet属性观擦器 代码完整如下:
class CircleView: UIView {
    var color:UIColor = UIColor.red {
        didSet {
            setNeedsDisplay() //告诉系统重绘界面
        }
    }
    override func draw(_ rect: CGRect) {
        let path = UIBezierPath(ovalIn: rect)
        color.setFill()
        path.fill()
    }
}
复制代码

这样就能显示了,希望大伙能多敲,多体会。 效果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是手势解锁的Demo实现过程: 1. 首先创建一个UIView的子类,作为手势解锁的主体视图,我们称之为`GestureLockView`。 2. 在`GestureLockView`中创建一个数组`circleArray`,用于存储手势解锁的圆点。 ``` @property (nonatomic, strong) NSMutableArray *circleArray; ``` 3. 在`GestureLockView`的`layoutSubviews`方法中,创建9个圆点,并加入到`circleArray`中。 ``` - (void)layoutSubviews { [super layoutSubviews]; CGFloat margin = (self.frame.size.width - 3 * kCircleSize) / 4.0; for (int i = 0; i < 9; i++) { CGFloat x = margin + (i % 3) * (margin + kCircleSize); CGFloat y = margin + (i / 3) * (margin + kCircleSize); CGRect frame = CGRectMake(x, y, kCircleSize, kCircleSize); GestureLockCircle *circle = [[GestureLockCircle alloc] initWithFrame:frame]; circle.tag = i + 1; [self addSubview:circle]; [self.circleArray addObject:circle]; } } ``` 4. 在`GestureLockView`中创建一个数组`selectedArray`,用于存储用户选择的圆点。 ``` @property (nonatomic, strong) NSMutableArray *selectedArray; ``` 5. 在`GestureLockView`中实现手势识别的方法`touchesMoved:withEvent:`,通过判断触摸点是否在圆点内来确定用户选择的圆点,并绘制用户选择的线条。 ``` - (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint point = [touch locationInView:self]; for (GestureLockCircle *circle in self.circleArray) { if (CGRectContainsPoint(circle.frame, point) && !circle.selected) { circle.selected = YES; [self.selectedArray addObject:circle]; break; } } self.currentPoint = point; [self setNeedsDisplay]; } ``` 6. 在`GestureLockView`中实现绘制方法`drawRect:`,根据用户选择的圆点绘制线条。 ``` - (void)drawRect:(CGRect)rect { if (self.selectedArray.count == 0) { return; } UIBezierPath *path = [UIBezierPath bezierPath]; path.lineWidth = kLineWidth; path.lineJoinStyle = kCGLineJoinRound; path.lineCapStyle = kCGLineCapRound; [[UIColor whiteColor] set]; for (int i = 0; i < self.selectedArray.count; i++) { GestureLockCircle *circle = self.selectedArray[i]; if (i == 0) { [path moveToPoint:circle.center]; } else { [path addLineToPoint:circle.center]; } } [path addLineToPoint:self.currentPoint]; [path stroke]; } ``` 7. 在`GestureLockView`中实现手势结束的方法`touchesEnded:withEvent:`,判断用户手势是否正确,并通过代理方法通知外部。 ``` - (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { NSMutableString *password = [NSMutableString string]; for (GestureLockCircle *circle in self.selectedArray) { [password appendFormat:@"%ld", circle.tag]; } BOOL success = [password isEqualToString:self.password]; if (success) { for (GestureLockCircle *circle in self.selectedArray) { circle.selected = NO; } [self.selectedArray removeAllObjects]; [self setNeedsDisplay]; if (self.delegate && [self.delegate respondsToSelector:@selector(gestureLockView:didCompleteWithPassword:)]) { [self.delegate gestureLockView:self didCompleteWithPassword:password]; } } else { for (GestureLockCircle *circle in self.selectedArray) { circle.selected = NO; circle.error = YES; } [self.selectedArray removeAllObjects]; [self setNeedsDisplay]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kErrorDuration * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ for (GestureLockCircle *circle in self.circleArray) { circle.error = NO; } [self setNeedsDisplay]; }); } } ``` 8. 在外部创建`GestureLockView`实例,并设置代理方法,实现手势解锁的逻辑。 ``` - (void)viewDidLoad { [super viewDidLoad]; GestureLockView *lockView = [[GestureLockView alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth, kScreenWidth)]; lockView.center = self.view.center; lockView.delegate = self; lockView.password = @"123456789"; [self.view addSubview:lockView]; } #pragma mark - GestureLockViewDelegate - (void)gestureLockView:(GestureLockView *)lockView didCompleteWithPassword:(NSString *)password { NSLog(@"password: %@", password); } ``` 至此,手势解锁的Demo已经完成了,你可以尝试在模拟器或真机上运行它。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值