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 의 높이 를 평평 하 게 이동 시 켜 왼쪽 상단 원점 의 좌 표를 얻 을 수 있다.
부록
이상 은 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Swift의 패스트 패스Objective-C를 대체하기 위해 만들어졌지만 Xcode는 Objective-C 런타임 라이브러리를 사용하기 때문에 Swift와 함께 C, C++ 및 Objective-C를 컴파일할 수 있습니다. Xcode는 S...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.