drawRect无效上下文

问题描述 投票:0回答:1

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

我有一个块可以成功提取起点/终点(我确实必须添加延迟以确保数组在开始之前具有值。我曾尝试将CGContextRef移到调度的内部和外部,但是我仍然收到“无效的上下文”。

我尝试过在没有运气的地方添加[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);

     */

    });

}
ios objective-c drawrect
1个回答
0
投票

    此无效上下文是您正在启动异步过程的结果,因此,在调用dispatch_after块时,提供给drawRect的上下文不再存在,并且您异步调用的块中也没有上下文抚摸这些线条。
  1. 但是视图不应启动此网络请求并进行解析。通常,视图控制器(或更好的其他网络控制器等)应该启动网络请求并进行解析。
  2. drawRect用于在给定的时间渲染视图。如果尚无要渲染的内容,则应立即返回。当数据可用时,可以为视图提供执行渲染和启动setNeedsDisplay所需的数据。

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

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

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

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

  • 这不太正确,因为我不太了解您的模型数据是什么,但是让我们假设一秒钟它只是返回一系列浮点值。因此,您可能会有类似的内容:


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

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

    然后

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

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

    但是希望这说明了这个想法:将视图从执行网络请求或解析数据的业务中脱颖而出。是否只渲染提供的任何数据。

    产生:

    enter image description here

  • © www.soinside.com 2019 - 2024. All rights reserved.