CGcontext и drawRect не рисуют

Нужна пара свежих глаз, мои перестали работать и больше не могут разобраться в собственном коде ...

Я пытаюсь создать приложение для рисования ручкой на бумаге в стиле рисования. Вот как это должно работать:

  • пользователь касается, приложение получает местоположение
  • переменная установлена, чтобы сообщить drawRect о настройке CGcontext, создании нового пути, переходе к точке и т. д. (потому что я всегда получаю ошибки / предупреждения в моем nslog всякий раз, когда я делаю что-либо с CGcontext вне метода drawRect)
  • Затем устанавливается var, чтобы определить, размещать ли точку или линию, когда пользователь поднимает палец.
  • если пользователь перетаскивает, переменная изменяется, чтобы указать drawRect рисовать линию, и вызывается [self setneedsdisplay]
  • каждый раз, когда пользователь кладет палец на экран, срабатывает таймер, каждые 5 секунд (или пока он не поднимет палец) приложение «захватывает» содержимое экрана, вставляет его в изображение и стирает экран, заменяя изображение и продолжает рисование (фактически кеширует изображение, поэтому все эти линии не нужно перерисовывать)

ОБНОВЛЕНИЕ №1 Я переписал его (потому что он был явно плохим и не работал ...) и получил следующее:

- (void)drawRect:(CGRect)rect {
    [_incrImage drawInRect:rect]; /* draw image... this will be blank at first 
    and then supposed to be filled by the Graphics context so that when the view 
    is refreshed the user is adding lines ontop of an image (but to them it looks 
    like a continuous drawing)*/
    CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapSquare);
    CGContextSetLineJoin(UIGraphicsGetCurrentContext(), kCGLineJoinRound);
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _brushW);
    CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [UIColor colorWithRed:_brushR green:_brushG blue:_brushB alpha:_brushO].CGColor);
    CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);
    CGContextAddPath(UIGraphicsGetCurrentContext(), _pathref);
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathStroke);
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    _drw = 2; //set this to draw a dot if the user taps
    UITouch *touch = [touches anyObject];
    _cp = [touch locationInView:self];
    _lp = _cp;
    CGPathRelease(_pathref); /* this line and the line below is to clear the 
    path so the app is drawing as little as possible (the idea is that as 
    the user draws and lifts their finger, the drawing goes into _incrImage 
    and then the contents is cleared and _incrImage is applied to the 
    screen so there is as little memory being used as possible and so the 
    user feels as if they're adding to what they've drawn when they touch 
    the device again) */
    _pathref = CGPathCreateMutable();
    CGPathMoveToPoint(_pathref, NULL, _lp.x, _lp.y);
    touch = nil;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    _drw = 1; //user moved their finger, this was not a tap so they want 
              //to draw a line
    UITouch *touch = [touches anyObject];
    _cp = [touch locationInView:self];
    CGPathAddLineToPoint(_pathref, NULL, _cp.x, _cp.y);
    [self setNeedsDisplay]; //as the user moves their finger, it draws
    _lp = _cp;
    touch = nil;
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    _cp = [touch locationInView:self];
    switch (_drw) { //logic to determine what to draw when the user 
                    //lifts their finger
         case 1:
            //line
            CGPathAddLineToPoint(_pathref, NULL, _cp.x, _cp.y);

            break;
         case 2:
            //dot
            CGPathAddArc(_pathref, NULL, _cp.x, _cp.y, _brushW, 0.0, 360.0, 1);

            break;

            default:
            break;
    }
    /* here's the fun bit, the Graphics context doesn't seem to be going 
    into _incrImage and therefore is not being displayed when the user 
    goes to continue drawing after they've drawn a line/dot on the screen */
    UIGraphicsBeginImageContext(self.frame.size);
    CGContextAddPath(UIGraphicsGetCurrentContext(), _pathref); /* tried adding
    my drawn path to the context and then adding the context to the image 
    before the user taps down again and the path is cleared... not sure 
    why it isn't working. */
    [_incrImage drawAtPoint:CGPointZero];
    _incrImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    [self setNeedsDisplay]; //finally, refresh the contents
    touch = nil;
}

- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [self touchesEnded:touches withEvent:event];
}

Моя новая проблема заключается в том, что все стирается при вызове drawRect и что _incrImage не получает содержимое текущей графики и не отображает их.

_ _old, я думаю, больше не нужен, но оставлю здесь для справки_ _ _

вот соответствующий код:

- (void)drawRect:(CGRect)rect {
/* REFERENCE: _drw: 0 = clear everything
                    1 = cache the screen contents in the image and display that
                        so the device doesn't have to re-draw everything
                    2 = set up new path
                    3 = draw lines instead of a dot at current point
                    4 = draw a 'dot' instead of a line
*/
CGContextSetLineCap(UIGraphicsGetCurrentContext(), kCGLineCapSquare);
CGContextSetLineJoin(UIGraphicsGetCurrentContext(), kCGLineJoinRound);
CGContextSetLineWidth(UIGraphicsGetCurrentContext(), _brushW);
CGContextSetStrokeColorWithColor(UIGraphicsGetCurrentContext(), [UIColor colorWithRed:_brushR green:_brushG blue:_brushB alpha:_brushO].CGColor);
CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeNormal);

switch (_drw) {
    case 0:
        //clear everything...this is the first draw... it can also be called to clear the view
        UIGraphicsBeginImageContext(self.frame.size);
        CGContextSetBlendMode(UIGraphicsGetCurrentContext(), kCGBlendModeClear);
        CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), [UIColor clearColor].CGColor);
        CGContextFillRect(UIGraphicsGetCurrentContext(), self.frame);
        CGContextFlush(UIGraphicsGetCurrentContext());
        _incrImage = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        [_incrImage drawAtPoint:CGPointZero];
        [_incrImage drawInRect:rect];
        break;
    case 1:
        //capture the screen content and stick it in _incrImage... 
        then apply_incrImage to screen so the user can continue drawing ontop of it
        _incrImage = UIGraphicsGetImageFromCurrentImageContext();
        [_incrImage drawAtPoint:CGPointZero];
        [_incrImage drawInRect:rect];
        break;
    case 2:
        //begin path and set everything up, this is called when touchesBegan: fires...
        _incrImage = UIGraphicsGetImageFromCurrentImageContext();
        [_incrImage drawAtPoint:CGPointZero];
        [_incrImage drawInRect:rect];
        CGContextBeginPath(UIGraphicsGetCurrentContext());
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _p.x, _p.y);
        break;
    case 3:
        //add lines, this is after the path is created and set...this is fired when touchesMoved: gets activated and _drw is set to draw lines instead of adding dots
        CGContextAddLineToPoint(UIGraphicsGetCurrentContext(), _p.x, _p.y);
        CGContextStrokePath(UIGraphicsGetCurrentContext());
        break;
    case 4:
        //this is fired when touchesEnd: is activated... this sets up ready if the app needs to draw a 'dot' or the arc with a fill...
        CGContextMoveToPoint(UIGraphicsGetCurrentContext(), _p.x, _p.y);
        CGContextAddArc(UIGraphicsGetCurrentContext(), _p.x, _p.y, _brushW, 0.0, 360.0, 1);
        CGContextFillPath(UIGraphicsGetCurrentContext());
        CGContextFlush(UIGraphicsGetCurrentContext());
        break;

    default:
        break;
  }
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    dTimer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(timeUP) userInfo:Nil repeats:YES];
    _drw = 2;
    UITouch *touch = [touches anyObject];
    _p = [touch locationInView:self];
    [self setNeedsDisplay];
    _drw = 4;
}
- (void)timeUP {
    _drw = 1;
    [self setNeedsDisplay];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    _drw = 3;
    UITouch *touch = [touches anyObject];
    _p = [touch locationInView:self];
    [self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event // (2)
{
    [dTimer invalidate];
    UITouch *touch = [touches anyObject];
    _p = [touch locationInView:self];
    [self setNeedsDisplay];
    [self timeUP];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [self touchesEnded:touches withEvent:event];
}

Мои вопросы:

  1. это эффективно? или есть лучший способ сделать это? и
  2. Почему это не рисунок? Я ничего не получаю, но вижу, как используется моя память ...

person blindman457    schedule 10.02.2014    source источник


Ответы (2)


Эгад.

Ваша первая проблема заключается в том, что setNeedsDisplay не рисует сразу, а просто отмечает вид, который нужно нарисовать в конце события. Итак, когда вы устанавливаете _drw = 2 и вызываете setNeedsDisplay, а затем устанавливаете _drw = 4, он будет фактически вызывать -drawRect: только с _drw = 4 (если это так, поскольку это все еще может быть не концом текущего события).

Но также не используйте этот переключатель _drw. Это не хорошо.

Вы хотите создать изображение и рисовать его по мере касания, а затем в drawRect: просто выводите изображение на экран. Если вы когда-нибудь обнаружите, что вызываете UIGraphicsGetImageFromCurrentImageContext () внутри -drawRect;, вы делаете что-то в обратном порядке (как вы здесь). Не срывайте изображение с экрана, создавайте изображение, которое вы на перетаскиваете на экран.

Экран никогда не должен быть вашим «состоянием». В этом безумие.

person Wil Shipley    schedule 10.02.2014
comment
Ах хорошо! Итак, как вы, очевидно, догадались, я могу быть немного новичком в этом, спасибо за советы! (отображение setneeds имеет смысл) Я думал, что мне нужно использовать какую-то логику, потому что каждый раз, когда я пытаюсь установить любой тип CGcontext вне метода drawRect, я могу указать CGcontext «все, что я пытаюсь сделать», ошибка недопустимого контекста .. Но спасибо за ясность. Итак, мой новый вопрос: как я могу создавать и обновлять только определенные части изображения (например, добавлять новые нарисованные линии), но также изменять все его (например, очищать его или изменять цвет каждой линии)? В любом случае спасибо за помощь! - person blindman457; 10.02.2014

Помимо ответа от @WilShipley, с которым я согласен (не помещайте логику управления состоянием в drawRect:, только чистую логику перерисовки), вы в настоящее время никогда не рисуете ничего, кроме крошечной линии, потому что CGContextStrokePath очищает текущую строку из контекста .

Контекст не предназначен для того, чтобы быть временным кешем незавершенных операций рисования, это ваш портал на экран (или резервное изображение / файл PDF / ...). Вам нужно создать состояние рисования вне drawRect:, а затем отобразить его на экране внутри drawRect:.

Чтобы облегчить проблемы с производительностью, перерисуйте область только вокруг последнего касания (или между последним и предыдущим касанием).

person Wain    schedule 10.02.2014
comment
да, у меня было подозрение, что управление состоянием было плохим ... но, как я уже сказал, я получаю ошибки при попытке добавить что-то в контекст вне метода drawRect ... И идея заключалась в том, что это не имело значения, если что-то было очищено, потому что все нарисованные точки должны были быть кэшированы в изображении, которое было нарисовано позади пользователя (поэтому им казалось, что они просто добавляли на холст). Просто вопрос, однако, как я могу узнать, какие разделы нужно только повторно - нарисовать и просто перерисовать эти части? - person blindman457; 10.02.2014
comment
Вам необходимо создать свой собственный контекст, если он находится за пределами drawRect: (CGBitmapContextCreate). Перерисовка основана на текущей точке и предыдущей точке, которой пользователь касался / касался (потому что именно там будет создан новый сегмент линии). - person Wain; 10.02.2014
comment
Хорошо, может быть, в данный момент я устал и не думаю, но как мне это сделать? и я плохо понимаю, как работает перерисовка, извините. - person blindman457; 10.02.2014
comment
stackoverflow.com/questions/8100845/ (создать область для перерисовки). Я бы сохранил переменную экземпляра, которая является «текущим путем», созданная, когда пользователь касается земли, измененная и уничтоженная, когда пользователь удаляет касание. Это используется, когда касание перемещается для создания / обновления вашего изображения (что может оказаться немного неэффективным, обратите внимание на создание нового изображения только после завершения касания). Draw rect просто рисует изображение (и, возможно, текущий путь). - person Wain; 10.02.2014