[iOS] 코어그래픽에 손대기

코어그래픽은 뷰에 그림을 그리는 기술이다. 하지만 C로 짜여진 API에 온갖 혼란스러운 함수명하며, 컨텍스트와 같은 어려운 개념들이 들어가면서 나와 같은 초보자에게는 마치 ‘넘을 수 없는’ 장벽과 같이 느껴진다. 하지만 많은, 정말 많은 튜토리얼들이 코어그래픽을 사용해서 그림을 그리는 이유는 명확하다. 바로 “아주 빠르기” 때문이다. 코어 그래픽은 그래픽 메모리의 버퍼를 직접 다루므로 아주 빠르다. (다만 일부 기기에서는 느리기도 하더라 ㅠㅠ) 오늘은 “시작하는 마음가짐”으로 코어 그래픽에 대해 잠깐 살펴보도록 하자.

코어그래픽을 사용하는데는 ‘컨텍스트’라는 개념을 이해해야 하는데, 이는 뒤에서 설명하기로 하고 잠시동안 ‘컨텍스트’는 그림을 그리는 도화지, 혹은 포토샵에서의 레이어와 같다고 간주하자.

drawRect:

뷰에 무언가를 그릴 때는 drawRect: 함수를 이용하게 된다. 뷰의 내용, 혹은 뷰의 하위 뷰의 어떤 그래픽적인 변경이 있어서 뷰를 업데이트 해야 할 필요가 있을 때 UIView의 이 메소드가 호출된다. 실제로 뷰에 어떤 내용이 그려지는 부분은 여기서 작업한다. 물론 뷰에 어떤 내용을 그리는 것은 다른 메소드를 만들어서 할 수도 있지만 여기서 해야 한다. 성능등의 이슈와 관련되어 있으므로, 이를 반드시 기억하도록 하자.

컨텍스트 찾아오기

그래픽을 그리는 장소는 다름아닌 컨텍스트라 했다. 뷰는 저마다의 컨텍스트를 가지고 있고, 여기에 패스나 비트맵을 추가해서 그래픽을 그릴 수 있다. 그래픽을 그리기 위해서는 컨텍스트가 필요한데, 이는 간단하게 다음과 같이 구할 수 있다.

-(void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphincGetCurrentContext();
    // 이제 뭔가를 그리면 된다. (응?)
}

컨텍스트의 상태 저장하기

단지 선 하나를 컨텍스트에 그리고자 할 때에도 여러가지 함수를 호출해야 한다. 이런 함수들은 선의 색은 무엇일지, 선의 굵기는? 선 끝의 모양은? .. 등등 그래픽의 속성에 대해 정의한다. 이런 함수들로 지금 그림을 그릴 컨텍스트의 속성을 지정한 후 그림을 그리게 된다. 즉 그림을 그리는 ‘상태’는 일종의 펜이나 붓과 같다고 할 수 있다. 하지만 한가지 펜이나 붓으로 모든 그림을 그리는 것은 아니다. 때에 따라서는 빨간선이나 굵은 선으로 그려야 할 때도 있는 것이다. 하지만 일부 선을 그릴 때 빨간펜을 쓴 다음, 다시 검은 펜으로 돌려놓지 않으면 그림의 상태가 뒤죽박죽이 될 수 있다. 따라서 컨텍스트의 상태를 저장하고, 원하는 상태를 세팅한 다음, 다시 복원하는 작업을 해 주어야 한다.

따라서 펜으로 그림을 그릴 때는 CGContextSaveGState(ctx);로 상태를 저장했다가, 펜을 바꿔 그림을 그리고 다시 CGContextRestoreGState(ctx);로 펜을 원상태로 복귀시키는 작업을 해주는 것이 좋다.

-(void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSaveGStete(ctx);
    // 그림을 그린다.
    CGContextRestoreGState(ctx);
}

UIImage를 바로 그리기

이미지나 패스를 그릴 때는 path를 그리는 CG함수를 사용하거나, CGImage 참조를 사용해서 그림을 그리는데, UIImageUIBezierPath 객체는 코어그래픽에 대응하는 메소드들을 가지고 있어서 단순한 선이나 그림을 보다 적은 코드를 사용해서 그릴 수 있다.

-(void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    UIImage *img = [UIImage imageNamed:@"myImage.png"];
    [img drawInRect:rect];
}

위 코드는 번들에 포함된 png 포맷의 이미지 파일을 UIImage 객체로 만들어서 이를 뷰에 바로 그려준다. Objective-C의 문법이지만 내부적으로는 코어그래픽를 사용하여 그림을 그린다.

패스를 좀 더 쉽게 만들고 그리기

패스는 선이나 도형을 그리는데 사용하는데, CGPath 구조체를 만들고, C함수를 사용해서 path를 구성한 후 컨텍스트에서 패스를 그릴 수 있다. 하지만 이 path는 한 번 그리고 나면 새로 만들어야 하므로, 재사용이 매우 어렵다. 이런 경우 UIBezierPath 객체를 사용하면 패스를 Objective-C 객체로 만들어 패스를 구성하고, 이를 적절한 컨텍스트에 그리고 옵션 (색이나 굵기등)을 바꿔서 쉽게 재사용할 수 있다.

-(void)drawRect:(CGRect)rect {
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGContextSetStrokeColorWithColor(ctx, [UIColor blackColor].cgColor]);
    CGContextSetLineWidth(ctx,3);
    UIBezierPath *path;
    path = [UIBezierPath
        bezierPathWithRoundedRect:rect
        byRoundingCorners:
        (UIRectCornerTopLeft
         |UIRectCornerTopRight
         |UIRectCornerBottomLeft
         |UIRectCornerBottomRight)
        cornerRadii:10.0];
    [path stroke];
}

UIBezierPath는 선, 곡선(arc), 사각형, 원, 둥근 사각형, 원호(Arc)의 형태로 생성할 수 있으며, 추가로 직선 및 곡선을 추가해서 복잡한 모양을 만들 수 있다. 자세한 내용은 UIBezierPath Class Reference를 참고하면 된다.