Languages/Objective-C2011.01.17 16:18
보통 Zoom 효과를 구현할 때 많이 사용하는 것이 UIScrollView입니다. 특히 PDF의 확대 효과에 관해서라면 Apple Developer Center에서 제공하는 ZoomingPDFViewer 예제를 많이들 참고하시죠.

이 예제에서는 세 개의 뷰를 사용해서 Zoom 효과를 구현합니다. 일단 백그라운드에 pdf를 먼저 그리구요. 그 앞에 zooming을 구현할 pdf view 두 개를 둡니다. 하나는 확대 이전 뷰, 하나는 확대 다음 뷰. (간단히 설명하자면 그렇습니다) zooming이 진행중인 동안에는 확대 이전 뷰를 사용해서 rendering하다가, 확대가 끝나고 나면 확대 다음 뷰를 확대된 크기만큼 새로 만들어서 (그러기 위해서는 얼마나 확대되었는지를 기억하고 있어야 합니다) 뷰가 보이는 계층 맨 위쪽에 붙여 버리는 것이죠. 

그런데 이 세 개의 뷰가 정말 필요한 걸까요? 이건 ZoomingPDFViewer 예제를 처음 봤을때 제 스스로 던진 질문이었습니다. 이 문제를 풀기 위해서, 과연 zooming이 진행 중일때 scroll view의 content view에 해당하는 뷰 (그러니까 확대 대상인 뷰)에 어떤 일이 벌어지는 지를 살펴봤습니다.

결론부터 이야기하자면, content view의 frame 속성(CGRect 값입니다)이 끊임없이 변합니다. 하지만 content view의 bounds 속성 값은 전혀 변하지 않습니다. 그러니, 다음과 같이 했다고 해 봅시다.

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView

{

    return pdfView;

}


위의 코드는 확대 대상 content view를 scroll view에게 반환하기 위해 구현하는, UIScrollViewDelegate 프로토콜 함수의 코드입니다. pdfView는 self.bounds를 사용해 자기 뷰 내부를 렌더링합니다. 그런데 frame 객체의 크기는 zooming을 할수록 자꾸 커진단 말입니다. 그래도 self.bounds에는 변함이 없으니, 뷰가 커지면 커질수록 뷰 안에 그려진 내용은 해상도가 떨어져 보이게 됩니다. 

그럼 대체 scroll view는 self.bounds는 가만 냅두면서 self.frame은 어떻게 계속 뻥튀기하는 걸까요?

답은 pdfView의 (그러니까 UIView를 상속한 뷰 클래스 객체라면 누구나 갖고 있는) transform 프라퍼티에 숨어 있습니다. 이 프라퍼티의 값이 CGAffineTransformIdentity가 아닐 경우, frame 프라퍼티의 값은 계산된 값입니다. 보통은 frame이나 bounds나 값이 동일하지만, transform 프라퍼티의 값이 CGAffineTransformIdentity가 아닌 경우에는 frame은 bounds 프라퍼티에 transform을 적용해서 구해진 CGRect 값이 되는 것이죠.

그래서 다음과 같은 코드 안에서 frame의 값과 bounds의 값을 찍어보면 그 값이 서로 다르게 나옵니다.

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale

{

// ...

NSLog(@"scale = %f, pdfScale = %f", scale, pdfScale);

NSLog(@"frame = %f, %f, %f, %f", view.frame.origin.x, view.frame.origin.y, view.frame.size.width, view.frame.size.height);

NSLog(@"bounds = %f, %f, %f, %f", view.bounds.origin.x, view.bounds.origin.y, view.bounds.size.width, view.bounds.size.height);

// ...

}


그러니까 결국 UIScrollView는 content view의 transform 프라퍼티의 값을 적절히 변경해서, content view의 확대/축소를 구현한다는 결론을 내릴 수 있는 것이죠. (거기다 contentOffset 프라퍼티까지 함께 수정/적용해서 말이죠.)

자. 그럼 여벌의 view 없이도 확대 동작을 구현하려면, ZoomingPDFViewer의 코드를 어떻게 개선해야 하는 걸까요? transform 프라퍼티를 적당히 매만지도록 코드를 변경하면 되겠군요. 지금부터는 여러분께서 ZoomingPDFViewer의 코드를 같이 보고 계신다고 가정하고 설명을 진행하겠습니다. 우선, backgroundImageView를 scroll view의 서브 뷰로 붙이는 부분을 아예 없애 버립니다. 필요 없으니까요.

그런 다음, oldPDFView를 참조하는 코드도 전부 없애 버립니다. 역시 필요 없습니다. (그러면 아마 scrollViewWillBeginZooming 안의 코드가 전부 날아가 버릴겁니다.) 

자. 그런 다음에는 pdfView만 필요하니까, PDFScrollView의 initWithFrame 메소드 안에서 pdfView를 생성하는 부분의 코드를 다음과 같이 고칩니다.

pdfView = [[TiledPDFView alloc] initWithFrame:pageRect andScale:pdfScale];

[pdfView setPage:page];

[self addSubview:pdfView];


이렇게 한 다음에, scrollViewDidEndZooming의 코드를 다음과 같이 고칩니다.

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView 

         withView:(UIView *)view atScale:(float)scale

{

// set the new scale factor for the TiledPDFView

pdfScale *=scale;

pdfView.bounds = CGRectMake(0,0,

                      view.frame.size.width, view.frame.size.height);

        // make the frame size to the bounds size

view.transform = CGAffineTransformIdentity

       

[pdfView setNeedsDisplay];

}


그런 다음, TiledPDFView의 initWithFrame의 코드를 다음과 같이 수정합니다.

tiledLayer.levelsOfDetail = 1;

tiledLayer.levelsOfDetailBias = 1;

tiledLayer.tileSize = CGSizeMake(512, 512);


detail 레벨을 1로 주어야 줌이 된 다음에 해상도가 상승되는 것이 보이게 되더군요. 너무 높으면 마치 줌이 되는 동안에서 해상도가 상승되다가, 마지막에 뷰가 쓸데없이 다시 그려지는 것 처럼 보입니다.

이렇게 한 다음에 실행시켜 보면, 이제 뷰 하나만 가지고도 PDF 문서의 zooming, 그러니까 확대/축소가 정상적으로 처리됩니다. scrollViewDidZooming 안에서 bounds의 값이 frame과 같도록, transform 프라퍼티의 값을 강제로 CGAffineTransformIdentity로 변경했기 때문입니다. 

자. 그런데 한가지 문제가 남았습니다. 뭔가요?

네. 아마 이 예제를 한번이라도 실행해 본 분이라면 아실텐데요. 이 문제는 이전 버전이나 현 버전이나 똑같습니다. 이 문제는 content view의 크기가 계속 변하기 때문에 발생하는 문제입니다. content view가 예전과 동일한 instance를 가리키고 있고 bounds의 값에도 변화가 없는 경우에는 scrollViewDidEndZooming 메소드의 atScale:(float)scale 인자값 'scale'의 값이 항상 'content view의 원래 크기 대비, 현재 크기의 비율'을 나타내게 되는데, content view instance가 확대할 때 마다 계속 달라지거나 (종전 예제의 경우) bounds의 값이 똑같이 유지되지 않는 경우(현 예제의 경우)에는 인자값 'scale'의 값이 '종전 크기 대비, 현재 크기의 비율'을 나타내게 된다는 것이죠. 

따라서 scroll view의 maximumZoomScale 프라퍼티나 minimumZoomScale 프라퍼티 값을 제한하는 것으로는 '어디까지 확대하고 어디까지 축소할 것인지'를 결정할 수 없게 됩니다. 

그러니 scrollViewWillBeginZooming과 scrollViewDidEndZooming 메소드 안에서 pdfScale 값을 가지고 minimumZoomScale 프라퍼티와 maximumZoomScale 프라퍼티를 가지고 꽁수를 좀 부려야 됩니다. 

이에 관해서는 각자 알아서 해보시는 것으로 하고, (연습문제라고 해도 좋겠습니다. ㅎㅎ) ZoomingPDFViewer 예제 분석과 개선에 관한 이번 글은 마치도록 하죠. 

신고
Posted by 이병준

소중한 의견, 감사합니다. ^^

  1. 띵호

    좋은 글 잘 읽었습니다.

    님이 남겨주신 문제가 딱 제가 가지고 있는 문제네요 ㅋㅋㅋㅋ
    근데 pdfScale를 가지고 계속 씨름 중인데. 혹시..아닌가요??ㅜㅜ

    2012.05.29 02:27 신고 [ ADDR : EDIT/ DEL : REPLY ]
    • 띵호

      와우 거의 한시간 동안 씨름해봤는데....
      문제를 해결했습니다.
      머가 실마리인지 몰랐는데 minimumzommScale 과 maximumZoomScale 를 가지고 노니 해결되네요 ㅋㅋ

      감사합니다 수고하세요~~

      2012.05.29 03:00 신고 [ ADDR : EDIT/ DEL ]