drawRect Неверный контекст - PullRequest
1 голос
/ 06 марта 2020

Попытка нарисовать график в 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);

     */

    });

}

1 Ответ

0 голосов
/ 06 марта 2020

Пара наблюдений:

  1. Этот недопустимый контекст является результатом того, что вы инициируете асинхронный процесс, поэтому к тому времени, когда вызывается блок dispatch_after, предоставляется контекст на drawRect больше не существует, и у вашего асинхронно вызываемого блока нет контекста, по которому можно перемещаться по этим строкам.

    Но представление не должно инициировать этот сетевой запрос и анализ. Обычно контроллер представления (или, лучше, другой сетевой контроллер и т. П.) Должен инициировать сетевой запрос и анализ.

  2. drawRect предназначен для визуализации представления при заданном момент времени. Если еще нечего рендерить, он должен сразу вернуться. Когда данные доступны, вы предоставляете представлению данные, необходимые для рендеринга, и запускаете setNeedsDisplay.

  3. Таким образом, общий шаблон будет иметь свойство в вашем подклассе представления и иметь установщик для этого свойства, вызывая для вас setNeedsDisplay.

  4. Вместо того, чтобы инициировать асинхронный запрос и пытаться использовать данные в течение двух секунд (или любого произвольного промежутка времени), вы вместо этого задаете downloadData параметр блока обработчика завершения, который он вызывает после завершения загрузки, и сразу же запустить обновление, как только загрузка и анализ будут завершены. Это позволяет избежать ненужных задержек (например, если вы ждете две секунды, но получаете данные за 0,5 секунды, зачем ждать дольше, чем необходимо; если вам нужно две секунды, но получите данные за 2,1 секунды, вы рискуете не иметь никаких данных для отображения). Инициируйте обновление представления точно после завершения загрузки и анализа.

  5. Эта ссылка float * является локальной переменной и никогда не будет заполнена. Ваш downloadData, вероятно, должен вернуть необходимые данные в вышеупомянутом обработчике завершения. Честно говоря, это понятие указателя на массив C не является шаблоном, который вы должны использовать в Objective- C, в любом случае. Если ваш сетевой ответ действительно возвращает только два числа с плавающей точкой, это то, что вы должны передать этому представлению, а не float *.

  6. Обратите внимание, я заменил код CoreGraphics на рисунок UIKit , Лично я был бы склонен к go дальше и перешел бы к CAShapeLayer и вообще не имел бы drawRect. Но я не хотел бросать в тебя слишком много. Но общая идея заключается в том, чтобы использовать наивысший уровень абстракции, насколько это возможно, и нет необходимости разбираться в сорняках CoreGraphics для чего-то столь же простого, как это.


Это будет не совсем правильно, так как я не совсем понимаю, каковы данные вашей модели, но давайте на секунду предположим, что это просто возвращает серию float ценности. Таким образом, у вас может быть что-то вроде:

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

Теперь я оставлю это до вам построить NSArray из NSNumber значений, так как это совершенно отдельный вопрос. И хотя перемещение этой сети / кода разбора из вида в контроллер представления немного лучше, он, вероятно, даже не принадлежит. У вас может быть другой объект, предназначенный для выполнения сетевых запросов и / или анализа результатов. Но, опять же, это, вероятно, выходит за рамки этого вопроса.

Но, надеюсь, это иллюстрирует идею: получить представление о бизнесе по выполнению сетевых запросов или анализу данных. Пусть он просто отобразит все предоставленные данные.

Это дает:

enter image description here

...