drawRect无效上下文 [英] drawRect Invalid Context

查看:79
本文介绍了drawRect无效上下文的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

尝试在 UIView 中从服务器下拉的值绘制图形。

Trying to draw a graph in UIView from values pulled down from a server.

我有一个块可以成功提取起点/终点(我确实必须添加延迟以确保数组在开始之前具有值。我已经尝试将 CGContextRef 移到调度程序的内部和外部,但是我仍然收到'Invalid Context'。

I have a block that is successfully pulling the start/end points (I did have to add the delay to make sure the array had the values before commencing. I've tried moving the CGContextRef both inside and outside the dispatch but I still get 'Invalid Context'.

在各个地方添加 [self setNeedsDisplay];

这里是代码:

- (void)drawRect:(CGRect)rect {

    // Drawing code

    // Array - accepts values from method
    float *values;

    UIColor * greenColor = [UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
    UIColor * redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];

    // Call to method to run server query, get data, parse (TBXML), assign values to array
    // this is working - NSLog output shows proper values are downloaded and parsed...
    values = [self downloadData];

    // Get context
    CGContextRef context = UIGraphicsGetCurrentContext();
    NSLog (@"Context: %@", context);

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    NSLog(@"Waiting for array to populate from URL/Parsing....");


    NSLog(@"length 1: %f", values[0]);
    NSLog(@"length 2: %f", values[1]);



    float starty = 100.0;
    float startleft = 25.0;

    CGContextSetLineWidth (context, 24.0);

    CGContextSetStrokeColorWithColor (context, greenColor.CGColor);

    CGContextMoveToPoint(context, startleft, starty);

    CGContextAddLineToPoint(context, values[0], starty);

    NSLog(@"Start/Stop Win values: %f", values[0]);

    CGContextStrokePath (context);

    starty = starty + 24.0;


    CGContextSetLineWidth (context, 24.0);

    CGContextSetStrokeColorWithColor (context, redColor.CGColor);

    CGContextMoveToPoint(context, startleft, starty);

    CGContextAddLineToPoint(context, values[1], starty);

    NSLog(@"Start/Stop Loss values: %f",  values[1]);

    CGContextStrokePath (context);

     */

    });

}


推荐答案

一对观察结果:


  1. 此无效上下文是您启动异步过程的结果,因此在调用dispatch_after 块,提供给 drawRect 的上下文不再存在,并且您异步调用的块也没有在其中描画这些行的上下文。

  1. This invalid context is a result that you’re initiating an asynchronous process, so by the time the dispatch_after block is called, the context supplied to drawRect no longer exists and your asynchronously called block has no context in which to stroke these lines.

但是视图不应启动此网络请求并进行解析。通常,视图控制器(或更好的是,其他一些网络控制器等)应该启动网络请求和解析。

But the view shouldn’t be initiating this network request and parsing. Usually the view controller (or better, some other network controller or the like) should be initiating the network request and the parsing.

code> drawRect 用于在给定的时间渲染视图。如果没有要渲染的内容,则应立即返回。当数据可用时,您向视图提供进行渲染所需的数据并启动 setNeedsDisplay

The drawRect is for rendering the view at a given moment in time. If there’s nothing to render yet, it should just return immediately. When the data is available, you supply the view the data necessary to do the rendering and initiate the setNeedsDisplay.

因此,一种常见的模式是在您的视图子类中具有一个属性,并为该属性设置方法为您调用 setNeedsDisplay

So, a common pattern would be to have a property in your view subclass, and have the setter for that property call setNeedsDisplay for you.

而不是启动异步请求并尝试在两秒钟(或任意时间)内使用数据,而是给您 downloadData 一个完成处理程序块参数,在完成下载时调用该参数,并在下载和解析完成后立即触发更新。这样可以避免不必要的延迟(例如,如果您等待两秒钟,但在0.5秒内获取数据,为什么要等待比需要的时间更长的时间;如果您希望两秒钟,但在2.1秒内获取数据,则可能会没有任何数据可以显示)。准确地在下载和解析完成后启动视图更新。

Rather than initiating the asynchronous request and trying to use the data in two seconds (or any arbitrary amount of time), you instead give your downloadData a completion handler block parameter, which it calls when the download is done, and trigger the updating immediately as soon as the download and parse is done. This avoids unnecessary delays (e.g. if you wait two seconds, but get data in 0.5 seconds, why wait longer than necessary; if you want two seconds, but get data in 2.1 seconds, you risk not having any data to show). Initiate the update of the view exactly when the download and parse is done.

float * 引用是一个局部变量,永远不会填充。您的 downloadData 可能应该在上述完成处理程序中返回必要的数据。坦白地说,这种指向C数组的指针的概念并不是您应该在Objective-C中使用的模式。如果您的网络响应确实只返回两个浮点数,那么您应该将此视图传递给该视图,而不是 float *

This float * reference is a local variable and will never get populated. Your downloadData probably should return the necessary data in the aforementioned completion handler. Frankly, this notion of a pointer to a C array is not a pattern you should be using in Objective-C, anyway. If your network response really returns just two floats, that’s what you should be passing to this view, not a float *.

注意,我已经用UIKit绘图替换了CoreGraphics代码。就我个人而言,我倾向于走到 CAShapeLayer ,而根本没有 drawRect 。但是我不想向你扔太多东西。但是一般的想法是尽可能使用最高级别的抽象,并且不需要像这样简单的事情就进入CoreGraphics的杂草。

Note, I’ve replaced the CoreGraphics code with UIKit drawing. Personally, I’d be inclined to go further and move to CAShapeLayer and not have a drawRect at all. But I didn’t want to throw too much at you. But the general idea is to use the highest level of abstraction as you can and there’s no need to get into the weeds of CoreGraphics for something as simple as this.




这并不完全正确,因为我不太了解您的模型数据是什么,但是让我们假设它只是返回一个浮点值系列。因此,您可能会遇到类似这样的情况:


This isn’t going to be quite right as I don’t really understand what your model data is, but let’s assume for a second it’s just returning a series of float values. So you might have something like:

//  BarView.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface BarView : UIView
@property (nonatomic, copy, nullable) NSArray <NSNumber *> *values;
@end

NS_ASSUME_NONNULL_END

//  BarView.m

#import "BarView.h"

@implementation BarView
    
- (void)drawRect:(CGRect)rect {
    if (!self.values) { return; }

    NSArray *colors = @[UIColor.greenColor, UIColor.redColor]; // we’re just alternating between red and green, but do whatever you want

    float y = 100.0;
    float x = 25.0;

    for (NSInteger i = 0; i < self.values.count; i++) {
        float value = [self.values[i] floatValue];
        UIBezierPath *path = [UIBezierPath bezierPath];
        path.lineWidth = 24;
        [colors[i % colors.count] setStroke];
        [path moveToPoint:CGPointMake(x, y)];
        [path addLineToPoint:CGPointMake(x + value, y)];
        [path stroke];

        y += 24;
    }
}

- (void)setValues:(NSArray<NSNumber *> *)values {
    _values = [values copy];
    [self setNeedsDisplay];
}
@end

请注意,这并不是在进行任何网络请求。它只是呈现提供给它的任何值。 的设置器将为我们触发 setNeedsDisplay

Note, this isn’t doing any network requests. It’s just rendering whatever values have been supplied to it. And the setter for values will trigger setNeedsDisplay for us.

然后

//  ViewController.h

#import <UIKit/UIKit.h>

NS_ASSUME_NONNULL_BEGIN

@interface ViewController : UIViewController

- (void)download:(void (^)(NSArray <NSNumber *> * _Nullable, NSError * _Nullable))completion;

@end

NS_ASSUME_NONNULL_END

并且

//  ViewController.m

#import "ViewController.h"
#import "BarView.h"

@interface ViewController ()
@property (nonatomic, weak) IBOutlet BarView *barView;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    [self download:^(NSArray <NSNumber *> *values, NSError *error) {
        if (error) {
            NSLog(@"%@", error);
            return;
        }

        self.barView.values = values;
    }];
}

- (void)download:(void (^)(NSArray <NSNumber *> *, NSError *))completion {
    NSURL *url = [NSURL URLWithString:@"..."];
    [[[NSURLSession sharedSession] dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // parse the data here

        if (error) {
            dispatch_async(dispatch_get_main_queue(), ^{
                completion(nil, error);
            });
            return;
        }

        NSArray *values = ...

        // when done, call the completion handler
        dispatch_async(dispatch_get_main_queue(), ^{
            completion(values, nil);
        });
    }] resume];
}

@end

现在,由您自己决定建立 NSNumber 值的 NSArray ,因为这是一个完全独立的问题。而且,虽然将网络/解析代码从视图中移出并移入视图控制器会更好一些,但它甚至可能不属于该位置。您可能还有另一个对象专门用于执行网络请求和/或解析结果。但这再次超出了这个问题的范围。

Now, I’ll leave it up to you to build the NSArray of NSNumber values, as that’s a completely separate question. And while moving this network/parsing code out of the view and into the view controller is a little better, it probably doesn’t even belong there. You might have another object dedicated to performing network requests and/or parsing results. But, again, that’s probably beyond the scope of this question.

但希望这可以说明这个想法:从执行网络请求或解析数据的业务中脱颖而出。

But hopefully this illustrates the idea: Get the view out of the business of performing network requests or parsing data. Have it just render whatever data was supplied.

产生以下结果:

这篇关于drawRect无效上下文的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆