iOS 좌표계 의 깊이 있 는 탐구

10880 단어 ios좌표계바꾸다
머리말
app 렌 더 링 보 기 를 할 때 좌표계 에서 그리 기 영역 을 지정 해 야 합 니 다.
이 개념 은 보기 에는 간단 한 것 같 지만 사실은 그렇지 않다.
When an app draws something in iOS, it has to locate the drawn content in a two-dimensional space defined by a coordinate system.
This notion might seem straightforward at first glance, but it isn't.
본문
우 리 는 먼저 가장 간단 한 코드 에 착안 하여 drawRect 에 일반적인 UILabel 을 표시 합 니 다.
판단 하기 편 하도록 전체 view 의 배경 을 검은색 으로 설정 합 니 다.

- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 CGContextRef context = UIGraphicsGetCurrentContext();
 NSLog(@"CGContext default CTM matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 28)];
 testLabel.text = @"    ";
 testLabel.font = [UIFont systemFontOfSize:14];
 testLabel.textColor = [UIColor whiteColor];
 [testLabel.layer renderInContext:context];
}
이 코드 는 먼저 UILabel 을 만 든 다음 텍스트 를 설정 하여 화면 에 표시 하고 좌 표를 수정 하지 않 았 습 니 다.
그래서UILabel.layer기본 좌표(0,0)에 따라 왼쪽 상단 에 그 렸 습 니 다.

UILabel 그리 기
이어서 우 리 는 CoreText 를 사용 하여 텍스트 를 렌 더 링 하려 고 시도 했다.

- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 CGContextRef context = UIGraphicsGetCurrentContext();
 NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"    " attributes:@{
             NSForegroundColorAttributeName:[UIColor whiteColor],
             NSFontAttributeName:[UIFont systemFontOfSize:14],
             }];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); //           CTFramesetterRef
 UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
 CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); //       
 CTFrameDraw(frameRef, context);
}
먼저 NSString 으로 부 텍스트 를 만 든 다음 부 텍스트 에 따라 CTFramesetterRef 를 만 들 고 CGRect 로 생 성 된 UIBezierPath 와 결합 하여 CTFrameRef 를 얻어 화면 에 렌 더 링 합 니 다.
그러나 결 과 는 윗글 과 일치 하지 않 았 다.문 자 는 위아래 가 뒤 바 뀌 었 다.

CoreText 텍스트 그리 기
이 서로 다른 현상 부터 우 리 는 iOS 의 좌표 계 를 이해한다.
좌표계 개념
iOS 에서 도형 을 그 리 려 면 2 차원 좌표계 에서 해 야 하지만 iOS 시스템 에 여러 개의 좌표계 가 존재 하기 때문에 좌표계 의 전환 을 처리 해 야 합 니 다.
먼저 그래 픽 컨 텍스트(graphics context)의 개념 을 소개 합 니 다.예 를 들 어 우리 가 자주 사용 하 는 CGContext 는 Quartz 2D 의 컨 텍스트 입 니 다.그림 컨 텍스트 는 그림 그리 기 에 필요 한 정 보 를 포함 합 니 다.예 를 들 어 색상,선 너비,글꼴 등 입 니 다.우리 가 윈도 에서 자주 사용 하 는 그림 을 참고 하여 우리 가 붓 을 사용 할 때🖌화이트 보드 에 글 씨 를 쓸 때 도형 상하 문 은 바로 붓 의 속성 설정,화이트 보드 크기,붓 의 위치 등 이다.
iOS 에서 모든 그래 픽 컨 텍스트 에는 세 가지 좌표 가 있 습 니 다.
1.좌표계(사용자 좌표계 라 고도 함)를 그립 니 다.우 리 는 평소에 사용 하 는 좌 표를 그립 니 다.
2.보기(view)좌표계,왼쪽 상단 을 원점(0,0)으로 고정 하 는 view 좌표계;
3.물리 좌표계,물리 스크린 의 좌표계,똑 같이 왼쪽 상단 을 원점 으로 고정 합 니 다.

우리 가 그린 목표 에 따라(화면,비트 맵,PDF 등)여러 context 가 있 습 니 다.

Quartz 에서 흔히 볼 수 있 는 그리 기 목표
context 에 따라 좌 표를 그 리 는 것 이 각각 다르다.예 를 들 어 UIKit 의 좌 표 는 왼쪽 상단 원점 의 좌표계 이 고 CoreGraphics 의 좌 표 는 왼쪽 하단 을 원점 으로 하 는 좌표계 이다.

CoreGraphics 좌표계 와 UIKit 좌표계 의 전환
CoreText 는 CoreGraphics 를 기반 으로 하기 때문에 좌표계 도 CoreGraphics 의 좌표계 이다.
우 리 는 앞에서 언급 한 두 가지 렌 더 링 결 과 를 돌 이 켜 보면 다음 과 같은 의문 이 생 긴 다.
UIGraphics GetCurrentContext 는 CGContext 를 되 돌려 줍 니 다.왼쪽 아래 가 원점 인 좌표계 임 을 의미 합 니 다.UILabel(UIKit 좌표계)로 renderInContext 를 직접 만 들 수 있 고'측'자 는 UILabel 의(0,0)위치 에 대응 하 며 왼쪽 위 에 있 습 니까?
CoreText 로 렌 더 링 할 때 좌 표 는(0,0)이지 만 렌 더 링 결 과 는 왼쪽 상단 에 있 고 왼쪽 아래 에 있 는 것 이 아 닙 니 다.그리고 글 자 는 위아래 가 뒤 바 뀌 었 다.
이 문 제 를 탐구 하기 위해 서 나 는 코드 에 log 를 한 줄 넣 었 다.

NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
그 결 과 는CGContext default matrix [2, 0, 0, -2, 0, 200]이다.
CGContextGetCTM 반환 은 CGAffineTransform 모방 변환 매트릭스 입 니 다.

2 차원 좌표계 의 점 p 는(x,y,1)로 표현 할 수 있 고 변 경 된 행렬 을 곱 하면 다음 과 같다.

결 과 를 곱 하여 아래 의 관 계 를 얻다.

이때,우 리 는 인쇄 결과[2,0,0,-2,0,200]를 다시 한 번 살 펴 보고 간략하게 할 수 있다.
x' = 2x, y' = 200 - 2y
렌 더 링 된 view 높이 가 100 이기 때문에 이 좌 표 는 원점 을 왼쪽 아래(0,100)의 좌표계 로 바 꾸 고 원점 이 왼쪽 위(0,0)의 좌표계 로 바 꾸 는 것 과 같 습 니 다!일반적으로 우 리 는 UIKit 를 사용 하여 렌 더 링 을 하기 때문에 iOS 시스템 은 drawRect 가 CGContext 로 돌아 갈 때 기본적으로 개발 자가 직접 UIKit 좌표계 로 렌 더 링 하 는 데 편리 하도록 변환 을 해 주 었 다.

우 리 는 시스템 에 추 가 된 좌표 변환 을 복원 하려 고 시도 합 니 다.
먼저 진행CGContextTranslateCTM(context, 0, self.bounds.size.height);x'=2x,y'=200-2y 에 대해 우 리 는 x=x,y=y+100;self.bounds.size.height=100
그래서 x'=2x,y'=200-2(y+100)=-2y 가 있 습 니 다.
더욱 진행한다CGContextScaleCTM(context, 1.0, -1.0);x'=2x,y'=-2y 에 대해 우 리 는 x=x,y=-y 를 사용 합 니 다.
그래서 x'=2x,y'=-2(-y)=2y 가 있다.

- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];
 CGContextRef context = UIGraphicsGetCurrentContext();
 CGContextTranslateCTM(context, 0, self.bounds.size.height);
 CGContextScaleCTM(context, 1.0, -1.0);
 NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"    " attributes:@{
             NSForegroundColorAttributeName:[UIColor whiteColor],
             NSFontAttributeName:[UIFont systemFontOfSize:14],
             }];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); //           CTFramesetterRef
 UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
 CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); //       
 CTFrameDraw(frameRef, context);
}
로그 로 도 알 수 있 습 니 다CGContext default matrix [2, 0, -0, 2, 0, 0];최종 결 과 는 다음 과 같 습 니 다.텍스트 는 왼쪽 아래 에서 렌 더 링 을 시 작 했 고 위아래 가 뒤 바 뀌 지 않 았 습 니 다.

이때 우 리 는 새로운 고민 이 생 겼 다.
CoreText 로 문자 의 상하 가 뒤 바 뀌 는 현상 을 해결 하지만 수 정 된 좌표계 UIKit 는 정상적으로 사용 할 수 없습니다.두 좌표 계 를 어떻게 호 환 합 니까?
iOS 는CGContextSaveGState()방법 으로 context 상 태 를 잠 정적 으로 저장 한 다음 CoreText 가 그 려 진 후CGContextRestoreGState ()를 통 해 context 의 변환 을 복원 할 수 있다.

- (void)drawRect:(CGRect)rect {
 [super drawRect:rect];

 CGContextRef context = UIGraphicsGetCurrentContext();
 NSLog(@"CGContext default matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 CGContextSaveGState(context);
 CGContextTranslateCTM(context, 0, self.bounds.size.height);
 CGContextScaleCTM(context, 1.0, -1.0);
 NSAttributedString *attrStr = [[NSAttributedString alloc] initWithString:@"    " attributes:@{
                         NSForegroundColorAttributeName:[UIColor whiteColor],
                         NSFontAttributeName:[UIFont systemFontOfSize:14],
                         }];
 CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef) attrStr); //           CTFramesetterRef
 UIBezierPath * bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, 100, 20)];
 CTFrameRef frameRef = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, 0), bezierPath.CGPath, NULL); //       
 CTFrameDraw(frameRef, context);
 CGContextRestoreGState(context);
 
 
 NSLog(@"CGContext default CTM matrix %@", NSStringFromCGAffineTransform(CGContextGetCTM(context)));
 UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 100, 20)];
 testLabel.text = @"    ";
 testLabel.font = [UIFont systemFontOfSize:14];
 testLabel.textColor = [UIColor whiteColor];
 [testLabel.layer renderInContext:context];
}
렌 더 링 결 과 는 다음 과 같 습 니 다.콘 솔 에서 출력 한 두 개의 matrix 는 모두[2,0,0,-2,0,200]입 니 다.
닥 친 문제
1.UILabel.layer 는 renderInContext 에서 frame 이 실 효 됩 니 다.
UILabel 을 초기 화 할 때 frame 을 설 정 했 지만 적용 되 지 않 았 습 니 다.

UILabel *testLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 20, 100, 28)];
이것 은 frame 이 이전 view 에서 좌표 의 오프셋 이기 때문에 renderInContext 에서 좌표 의 출발점 은 frame 과 무관 하기 때문에 bounds 속성 을 수정 해 야 합 니 다.

testLabel.layer.bounds = CGRectMake(50, 50, 100, 28);
2.renderInContext 와 drawInContext 의 선택
UILabel.layer 를 context 에 렌 더 링 할 때 drawInContext 를 사용 해 야 합 니까?renderInContext 를 사용 해 야 합 니까?

비록 이 두 가지 방법 이 모두 효력 이 발생 할 수 있 지만 선 을 그 리 는 부분의 내용 에 따라 판단 할 때 renderInContext 를 사 용 했 고 문제 1 은 바로 여기에 있 는 Renders in the coordinate space of the layer 로 문제점 을 찾 았 다.
3.CoreGraphics 좌표계 가 일치 하지 않 는 것 을 어떻게 이해 하면 그리 기 결과 에 이상 이 생 길 수 있 습 니까?
나의 이해 방법 은 우리 가 먼저 좌표계 가 바 뀌 는 상황 을 고려 하지 않 아 도 된다 는 것 이다.
아래 그림 은 상반부 가 일반적인 렌 더 링 결과 로 쉽게 상상 할 수 있다.
다음은 좌 표를 추가 하여 변환 한 후 좌 표 는 원점 이 왼쪽 상단 에 있 는 정점 으로 바 뀌 어 아래 그림 의 점선 에 따라 수직 으로 뒤 집 힌 셈 이다.

좌표계 변환 방식 에 따라 이해 할 수 있 고 왼쪽 아래 원점 의 좌 표를 Y 축 에 비해 수직 으로 뒤 집 은 다음 에 위로 height 의 높이 를 평평 하 게 이동 시 켜 왼쪽 상단 원점 의 좌 표를 얻 을 수 있다.
부록
  • Drawing and Printing Guide for iOS
  • Quartz 2D Programming Guide
  • 총결산
    이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.

    좋은 웹페이지 즐겨찾기