Xcode debugging trick

Xcode debugging trick

Xcode: One Weird Debugging Trick That Will Save Your Life

정확한 조건은 기억이 잘 안 나는데, 아무튼 exception에 브레이크포인트를 걸어놓으면 에러메시지가 안 뜨는 경우가 많다. 그럴때 po $arg1을 치면 왜 죽었는지를 알 수 있다. 이걸 exception breakpoint에 추가해 놓으면 걸릴 때마다 자동으로 나온다. 자세한 건 위 링크를 참고하자. 작지만 굉장한 팁이다!

'iOS' 카테고리의 다른 글

Xcode debugging trick  (0) 2015.09.23
iOS 9 대응  (0) 2015.09.23
HitTesting  (0) 2015.08.05
얼굴 인식 및 사진 똑바로 세우기  (0) 2015.07.23
iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31

iOS 9 대응

iOS 9 대응

으레 그렇듯이 이번 iOS 업데이트에 따라 iOS 9 대응을 했다. 어떤 앱이냐에 따라 대응할 부분이 달라지겠지만 fluttr에서는 크게 두 가지 정도만 대응해주면 됐다. 딥링크라고 표현하는것 같던데, 커스텀 url 스킴 사용이 iOS 9로 넘어오면서 제약이 생겨서 많이들 골머리를 앓고 있는 것 같지만 fluttr에는 해당하지 않는 부분이라 별다른 문제는 없었다. 별 건 아니지만 이번 작업을 마지막으로 한동안 iOS 개발은 안 하게 될 것 같아서 짤막하게나마 남겨본다.

App Transport Security

iOS 9로 들어오면서 https를 디폴트로 권장하게 되었다. Configuring App Transport Security Exceptions in iOS 9 and OSX 10.11 를 참조하자. https를 사용하지 않으면 에러가 난다. 단, 위 링크의 하단에 나오는 것처럼 https를 사용하지 않도록 설정할 수도 있다. 제일 마지막에 나오는대로 NSAllowsArbitraryLoadsYES로 설정하는 것이 가장 간단한 (하지만 보안은 취약한) 방법이다.

ATS off
stackoverflow; Transport Security has Blocked a cleartext HTTP 에서 가져왔다.

Bitcode

Bitcode는 LLVM IR이다. IR이란 최적화(optimazation)를 위한 중간 단계 코드로, 뭔지 잘 모르겠으면 LLVM on AOSABOOK 의 링크를 참조하자. 그런데 Xcode는 원래 LLVM기반이고, 당연히 bitcode를 써 왔는데 왜 갑자기 iOS 9에서 이슈가 될까?

stackoverflow의 한 답변에 따르면, 원래(iOS 8까지)는 이 bitcode의 최적화 후 해당 앱이 실행될 수 있는 모든 머신 환경, 즉 x86 32 and 64 bit modes (for the simulator) 와 arm6/arm7/arm7s/arm64 (for the device)에 대해 바이너리를 생성하여 하나의 파일로 합쳤다. 이를 fat binary라고 한다. 이름에서부터 느껴지지만 당연히 사이즈가 커질 수 밖에 없다.

iOS 9에 대응하는 Xcode 7에서는, 이 문제의 해결을 위해 ENABLE_BITCODE 옵션을 제공한다. 이 옵션은 앱스토어에 fat binary를 만들지 않고 bitcode binary를 업로드하고, 앱스토어는 이 bitcode binary를 받아 각 머신 환경별 바이너리를 생성한다. 그러면 사용자에게 이전보다 훨씬 작은 크기의 앱을 제공할 수 있다. 또한, 이론적으로는, LLVM 최적화기가 업데이트 되면 해당 부분을 즉각 반영할 수 있는 장점 또한 가지고 있다.

자, 그럼 그냥 쓰면 되는 거 같은데 무슨 대응이 필요하냐? 문제는 써드파티 프레임워크에 있다. 짐작컨대, 프레임워크를 fat binary 형태로 제공하는 경우 앱을 bitcode binary로 변환할 수 없기 때문에 앱이 사용하는 모든 프레임워크가 bitcode binary를 제공해야 한다. 나의 경우에는 구글맵이 해당 문제를 일으켰으며 1.10.2에서 대응했다고 하는데 업데이트 해도 마찬가지의 문제가 나서 결국 그냥 ENABLE_BITCODE 를 꺼버렸다. 앱 타겟의 Build SettingsEnable Bitcode옵션이 있다.

turn off bitcode
친절한 움짤을 참고하자.

기타

이 외에도 메인 윈도우에서 루트뷰컨트롤러를 필수적으로 요구하는 문제 등의 잡다한 문제가 있었으나 간단한 부분이기에 생략한다.

'iOS' 카테고리의 다른 글

Xcode debugging trick  (0) 2015.09.23
iOS 9 대응  (0) 2015.09.23
HitTesting  (0) 2015.08.05
얼굴 인식 및 사진 똑바로 세우기  (0) 2015.07.23
iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31

HitTesting

Hit-Testing in iOS

Hit-testing이란, 유저 인터랙션, 즉 hit이 들어왔을 때 이 hit을 받는 뷰를 판단하는 과정이다. 뷰들이 쌓인 관계는 트리 구조로 구성되어 있는데, 이 트리에서 루트부터 시작해서 reverse pre-order depth-first traversal 알고리즘을 통해 hit을 받는 노드(뷰)를 찾아낸다.

Hit-testing은 모든 터치(유저 인터렉션)마다 실행되며 그 결과로 어떤 뷰나 gesture recognizer가 해당 터치에 대한 정보를 담고 있는 UIEvent를 받는다.

Note: 이유는 모르지만 hit-testing은 연속적으로 여러번 불린다.

hit-testing이 끝나면 해당 터치를 받는 뷰가 hit-test view가 되고, 이 뷰는 모든 터치 이벤트(began, moved, ended, canceled 등)를 받는 UITouch오브젝트와 연동된다. 또한 이 뷰나 상위 뷰에 gesture recognizer가 붙어 있으면 마찬가지로 UITouch오브젝트와 연동된다. 그리고 나면 이제 hit-test view는 터치 이벤트들을 받기 시작한다.

중요한 점은 유저 인터렉션이 해당 뷰 밖으로 나가도 hit-test view는 변하지 않는다.

search

위 이미지는 터치가 되었을 때 view hierarchy 트리에서 hit-testing 과정을 보여주고 있다. 형제(sibling)들의 경우 역순(reverse)으로 검색함으로써 겹치는 부분이 있을 때는 더 오른쪽에 있는 나중에 추가한 뷰가 hit-test view로 선택된다.

이 알고리즘은 다음과 같이 코딩할 수 있다. 아래는 hitTest:withEvent: 메소드의 기본 구현체(implementation)다.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    if ([self pointInside:point withEvent:event]) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}
  • alpha가 0.01 이하인 경우 hidden으로 취급한다
  • pointInside:withEvent: 함수는 뷰 안에 해당 포인트가 포함되는지를 검사한다
  • hit-test view를 재귀적으로 찾아 리턴하고, 만약 없으면 nil을 리턴한다

코드가 어렵지 않으니 자세한 설명은 생략한다.

Common use cases for overriding hitTest:withEvent:

hitTest:withEvent:를 오버라이딩 하면 터치 이벤트를 받는 hit-test view를 변경할 수 있다.

Increasing view touch area

버튼이나 뷰에 탭 제스처를 붙였을 때, 해당 버튼의 크기보다 좀 더 큰 영역의 터치를 받아야 할 때가 자주 있다.
increase touch area

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    if (!self.isUserInteractionEnabled || self.isHidden || self.alpha <= 0.01) {
        return nil;
    }
    CGRect touchRect = CGRectInset(self.bounds, -10, -10);
    if (CGRectContainsPoint(touchRect, point)) {
        for (UIView *subview in [self.subviews reverseObjectEnumerator]) {
            CGPoint convertedPoint = [subview convertPoint:point fromView:self];
            UIView *hitTestView = [subview hitTest:convertedPoint withEvent:event];
            if (hitTestView) {
                return hitTestView;
            }
        }
        return self;
    }
    return nil;
}

위 소스는 inset을 상하좌우 -10씩 주어 터치 범위를 넓혀준다. 기본 구현체와 비교했을 때 pointInside:withEvent:CGRectContainsPoint()로 바뀐 것을 확인할 수 있다. 이를 이용하면 hitTest:withEvent:를 수정하지 않고 pointInside:withEvent:를 오버라이드하여 같은 효과를 구현할 수 있다.

- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    CGRect touchRect = CGRectInset(self.bounds, -10, -10);
    return CGRectContainsPoint(touchRect, point);
}

참고로, 두 경우 모두 이 뷰의 수퍼뷰가 확장 범위를 포함하여야 한다. 다시 말해 이 뷰의 터치 범위를 상하좌우 -10씩 확장했을 때 수퍼뷰가 그 범위를 포함하고 있어야 한다. 그렇지 않다면 수퍼뷰도 마찬가지로 hitTest:withEvent:를 오버라이딩 해 주어야 한다. 트리 구조로 위에서 타고 내려오므로 부모 노드에서 더이상 내려오지 않으면 아무리 자식 노드의 인식 범위를 확장한들 소용이 없다.

pointInside:withEvent:의 오버라이딩은 http://stackoverflow.com/a/13067285를 참고하였다.

Passing touch events through to views below

때로는 터치 이벤트를 무시하고 아래 뷰로 넘겨야 할 때가 있다. 예를 들면 전면에 알파값을 준 투명한 뷰로 덮는 경우. 이러한 뷰에서 터치 이벤트를 받지 않고 통과시켜 뒤에 있는 뷰가 동작할 수 있도록 할 수 있다.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitTestView = [super hitTest:point withEvent:event];
    if (hitTestView == self) {
        hitTestView = nil;
    }
    return hitTestView;
}

다만 view.userInteractionEnabled = NO; 와 무엇이 다른지는 잘 모르겠음

Passing touch events to subview

부모 뷰가 여러 자식 뷰 중 특정 자식 뷰를 지정해서 터치 이벤트를 넘기고 싶을 수 있다. 예를 들어, 이미지 캐러셀(carousel)을 생각해보자.

carousel

페이징을 위해 UIScrollView를 작은 크기(페이지 크기)로 만들고, 보이기는 다 보여야 하니까 clipsToBoundsNO로 설정한다. 이렇게 하고 나면 캐러셀이 잘 작동하지만 페이징을 위해 작게 만들어 놓은 UIScrollView때문에 해당 부분을 스크롤해야만 이미지가 넘어가는 문제가 있다. 이 문제를 해결하기 위해 부모뷰 어디를 터치해도 UIScrollView가 터치 이벤트를 받도록 오버라이딩 하자.

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
    UIView *hitTestView = [super hitTest:point withEvent:event];
    if (hitTestView) {
        hitTestView = self.scrollView;
    }
    return hitTestView;
}

참고

Hit-Testing in iOS
stackoverflow; Event handling for iOS - how hitTest:withEvent: and pointInside:withEvent: are related?
stackoverflow; UIButton: Making the hit area larger than the default hit area

'iOS' 카테고리의 다른 글

Xcode debugging trick  (0) 2015.09.23
iOS 9 대응  (0) 2015.09.23
HitTesting  (0) 2015.08.05
얼굴 인식 및 사진 똑바로 세우기  (0) 2015.07.23
iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31

얼굴 인식 및 사진 똑바로 세우기

지금 만들고 있는 fluttr 앱에 갤러리에서 사진을 불러오고, 얼굴인식을 해서 프레임을 씌워주는 기능이 필요하다. 얼굴인식 API는 iOS 5.0 부터 애플이 자체적으로 지원하고 있다.

사진 똑바로 세우기

// find orientation
UIImageOrientation orientation = UIImageOrientationUp;
NSNumber *orientationValue = [asset valueForProperty:@"ALAssetPropertyOrientation"];
if(orientationValue != nil)
    orientation = [orientationValue intValue];
NSLog(@"orientation : %ld", orientation);

UIImage *fullImage = [UIImage imageWithCGImage:asset.defaultRepresentation.fullResolutionImage scale:1.0 orientation:orientation];

ALAsset으로부터 사진의 방향을 알아내서 똑바로 세워서 fullImage에 넣는 코드다. 이렇게 하지 않고 그냥 어셋에서 불러와서 화면에 띄우면 방향이 다 뒤죽박죽이다. 아마 찍을 때의 방향을 보존하는 것 같다.

참고
UIImage from ALAsset: getting the right orientation
stackoverflow; iOS - UIImageView - how to handle UIImage image orientation

얼굴 인식하기

iOS 5.0 부터 애플이 빌트인 API를 제공한다. 공식 문서를 그대로 보고 했더니 잘 안 되서 삽질을 좀 했다.

// face recognition
CIImage* image = [CIImage imageWithCGImage:fullImage.CGImage];

CIDetector* detector = [CIDetector detectorOfType:CIDetectorTypeFace
                                          context:nil
                                          options:@{CIDetectorAccuracy: CIDetectorAccuracyLow}];

NSArray* features = [detector featuresInImage:image];
NSLog(@"[Face Recog] %@", features);

삽질을 한 이유는 여러가지지만, 그 중 하나는 CIImage, 즉 CoreImageUIKit간의 다른 좌표계산 방법이다. UIKit은 왼쪽 위부터 좌표를 계산하지만 CoreImage는 왼쪽 아래가 기준이다. 따라서 변환이 필요하다.

CGAffineTransform transform = CGAffineTransformMakeScale(1, -1); // CIImage와 UIKit은 좌표기준이 달라서 변환해 주어야 함
transform = CGAffineTransformTranslate(transform, 0, -fullImage.size.height);

그리고 나서 내가 이미지를 보여줄 이미지뷰의 사이즈와 fullImage의 사이즈가 다르므로 이것도 맞추어 준다. 이 작업도 CGAffineTransform으로 하려고 했는데 실패해서 그냥 나누어주었다.

for(CIFaceFeature *faceFeature in features){
    CGRect faceRect = CGRectApplyAffineTransform(faceFeature.bounds, transform);
    UIView *faceView = [[UIView alloc] initWithFrame:faceRect];
    faceView.layer.borderWidth = 1;
    faceView.layer.borderColor = [[UIColor redColor] CGColor];
    float widthRatio = fullImage.size.width / imagev.width;
    float heightRatio = fullImage.size.height / imagev.height;
    faceView.x /= widthRatio;
    faceView.width /= widthRatio;
    faceView.y /= heightRatio;
    faceView.height /= heightRatio;
    [imagev addSubview:faceView];
}

끝!


(은가은이라는 신인 가수 사진으로 http://news.donga.com/3/all/20140204/60577578/9 에서 가져왔다)


참고
CoreImage and UIKit coordinates
Tutorial: Easy Face Detection With Core Image In iOS 5 (Example Source Code Provided)

'iOS' 카테고리의 다른 글

iOS 9 대응  (0) 2015.09.23
HitTesting  (0) 2015.08.05
얼굴 인식 및 사진 똑바로 세우기  (0) 2015.07.23
iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31
iOS Library: FastImageCache  (0) 2015.02.22

iOS Crash Reporting Tools 소개

Overview of iOS Crash Reporting Tools: Part 1/2

iOS의 Crash Reporting에 대해 알아보자. 항상 요약번역한다고 하고 전체번역에 가까웠던거 같은데 이번엔 정말 요약번역에 도전한다!

원문이 2년 전(2013년 5월) 글이라서 조금 아쉽긴 한데 전체적으로 살펴보기에 이게 제일 좋을 것 같다. 참고로 파트 2에서는 각 툴을 설치하고 사용하는 방법을 소개하는데, 거기까진 필요없을 것 같다.

이 글에서는 전체적인 크래시 리포팅 툴들에 대해 알아본다.

Introduction

이 시리즈에서는 크래시 리포팅 툴들을 살펴보고, 무료/유료 여부, 장점/단점 등을 알아본다. 첫번째 파트를 보고 나면 너에게 필요한 툴을 선택할 수 있을 것이고, 두번째 파트를 보고 나면 각 툴들을 어떻게 쓰는지 알 수 있을 것이다.

Crash Reporting Basics

크래시 리포팅 툴은 두 요소로 구성된다: 리포팅 라이브러리 + 서버사이드 콜렉터. 당연히 둘 다 중요하다. 리포팅 라이브러리는 크래시를 대비하고(서버로 보내고), 서버사이드 콜렉터는 크래시 데이터를 모으고 유의미한 정보로 변환한다.

Symbolication

크래시 리포트는 어플리케이션이 종료될 때 작동하던 모든 쓰레드의 스택 트레이스로 구성된다. 이 트레이스는 디버깅 할 때 볼 수 있는 정보와 비슷한데, 다만 심볼(인스턴스 및 메소드)의 이름에서 차이가 난다. 릴리즈 모드로 빌드하면 심볼 이름들은 바이너리 형태로 변환되고, 따라서 우리가 받는 크래시 리포트는 심볼 이름 대신에 16진수 주소가 적혀 있다.

iOS 디바이스를 맥에 연결하고, Xcode에서 Organizer를 열어서, 연결한 디바이스를 찾아 “Device Logs”를 선택하면 이러한 16진수 주소로 표현된 크래시 로그를 볼 수 있다:

crash log

위에서 일부는 심볼이고 (e.g. -[__NSArrayI objectAtIndex:]) 일부는 16진수 주소 (e.g. 0x000db142 0xb1000 + 172354) 인 것을 확인할 수 있다. 이는 partially symbolicated crash log라고 하는데, 크래시가 발생한 스택 트레이스의 꼭대기(top)에서 이 크래시를 생성한 구체적인 메소드까지 타고 내려간다.

이렇게 로그가 부분적으로 심볼화(partly symbolicated log) 되는 것은 Xcode가 UIKit이나 CoreFoundation같은 시스템 컴포넌트들만 심볼화 할 수 있기 때문이다(라인 6~18처럼).

라인 3의 심볼화되지 않은(un-symbolicated) 부분인 0x000db142 0xb1000 + 172354가 무엇을 의미하는지 살펴보자. 0x000db142 = 0xb1000 + 172354 인데, 여기서 크래시가 발생했다는 뜻이다. 여기서 0xb1000은 앱의 시작 어드레스고, 172354는 시작 위치로부터의 현재 위치라고 할 수 있다.

그러나 이 raw crash report로는 디버깅을 할 수가 없으니, 이를 “symbolication”을 통해서 raw number들을 정확히 어떤 코드인가로 매칭하자.

The Symbolication Process

심볼화를 위해서는 두가지가 필요하다: 크래시를 낸 어플리케이션 바이너리와 바이너리의 컴파일 과정에서 생성되는 dSYM 파일. Xcode에서 “Product/Archive”를 사용하면, Xcode가 이 파일들을 저장한다. Window/Organizer/Archive tab에서 확인할 수 있다. 네가 지금까지 빌드를 위해 “Build and Archive”를 사용했다면 지금까지의 정보도 저장되어 있을 것이다.

Find that dSYM!

Window/Organizer/Archive tab에 가 보면 왼쪽에 우리가 아카이브한 앱들을 볼 수 있다. 앱을 오른쪽 클릭해서 show in finder옵션을 통해 파인더에서 열자. .xcarchive 파일(폴더)이 보일 것이다.

이 아카이브를 살펴보기 위해 터미널로 이동하자. 터미널에 이 폴더를 드래그하여 폴더 경로를 따고, Ctrl+A로 앞으로 이동한 뒤 cd를 쳐 주면 손쉽게 아카이브 폴더로 이동할 수 있다. 여기서 find . -type d명령을 통해 하위 폴더들을 살펴볼 수 있다.

$ find . -type d
.
./Products
./Products/Applications
./Products/Applications/breezi.app
./Products/Applications/breezi.app/_CodeSignature
./Products/Applications/breezi.app/en.lproj
./dSYMs
./dSYMs/breezi.app.dSYM
./dSYMs/breezi.app.dSYM/Contents
./dSYMs/breezi.app.dSYM/Contents/Resources
./dSYMs/breezi.app.dSYM/Contents/Resources/DWARF

“.” 은 현재 폴더, “-type d”는 디렉토리를 의미한다. 즉 현재 위치로부터 하위 디렉토리들을 찾으라는 명령이다. 결과 중 “~.app.dSYM” 폴더가 우리가 심볼화를 위해 찾고자 하는 폴더다. 이 .dSYM이 바이너리로부터 심볼들을 복구할 수 있는 모든 정보를 갖고 있다.

Xcode는 자체적으로 심볼화를 지원한다. .dSYM파일을 찾기 위해 spotlight를 사용하는데, 가끔 파일이 컴퓨터에 없다던가 할 경우 문제가 생길 수 있다. 이럴 때 .dSYM파일을 찾아서 직접 심볼화 할 수 있다. Organizer/Devices tab/Device Logs로 가서 심볼화 할 크래시 로그를 선택하고 Re-Symbolicate 버튼을 누르면 된다.

re-symbolicate

Xcode 뿐만 아니라 크래시 리포팅 툴들도 전부 .dSYM파일을 사용한다. 이 파일이 없다면 크래시 로그를 갖고 있어도 아무 의미가 없다.

Making a Case for Crash Reporting

.dSYM을 찾은 것 처럼, 투박하지만 크래시 리포팅 툴이 없을 때 수동으로 크래시 로그도 찾을 수 있다. 디바이스를 맥에 연결하고, OS별 크래시 로그 폴더를 찾은 뒤, “.crash” 확장자를 가진 파일을 찾는다. 이 파일을 가져와서 Xcode의 Organizer로 열면 “Device Logs” 탭에서 확인 및 심볼화(re-symbolication) 할 수 있다.

이 방법의 가장 큰 문제는 크래시가 난 디바이스가 있어야 한다는 것이다. 바꿔 말하면 유저가 직접 크래시 로그를 모아 너에게 보내줘야 한다!

다행히도, 애플은 앱스토어를 통해 배포된 앱들이 크래시 리포트를 모을 수 있도록 해 준다. 단, 이 방법은 “진단 데이터를 자동으로 애플로 전송” 옵션에 동의한 유저에 한해서다. 앱스토어에 등록한 앱이 있다면, 아이튠즈 커넥트로 가서, “Manage Your Applications -> 앱 선택 -> View details -> Crash Reports” 를 선택하자.

crash report

여기서 애플이 모은 크래시 로그들을 볼 수 있다. 많은 사람들이 진단 데이터를 애플로 전송하는 데에 동의하지 않기 때문에 크래시 리포팅 툴을 사용하는 것에 비해서는 적은 크래시 로그들만이 확인 가능하다. 또한, 임포팅(importing) 및 심볼화(symbolicating) 등 추가 작업이 필요하다.

자, 그럼 지금까지 살펴본 작업들을 포괄적으로 제공하는 크래시 리포팅 툴들을 살펴볼 차례다. 위에서도 적었지만 원문이 2년 전 글이므로 많이 달라졌을 수 있다. 참고로 아래 툴 중 TestFlight는 애플이 인수했다!

  • Crashlytics
  • Crittercism
  • Bugsense
  • TestFlight
  • HockeyApp

Crashlytics (무료)

최근에 트위터에 인수된 Crashlytics는 가장 유명한 크래시 리포트 툴 중 하나다. 홈페이지에 가 보면 Path, Yammer, yelp, PayPal, Walmart 등등 짱짱한 회사들이 이걸 쓰고 있다. 클라이언트 / 서버사이드 프레임워크를 모두 지원하는 풀스택 서비스다. 원래 무료가 아니었던것 같은데 트위터가 인수하면서 무료로 바뀐 것 같다!

crashlytics

대시보드에서 잘 정리된 데이터를 볼 수 있다. 또한 크래시 리포트를 선택하면

crash report

이와 같이 심볼화 된 리포트를 볼 수 있다! 최상단에서는 크래시가 날 때의 환경이 나온다.

Crittercism

Pricing plan. B2C/MAU 기준 30,000명 까지 무료. 프로 플랜은 더 많은 기능을 지원하고 유료. 한국어로 검색하면 거의 자료가 없는 걸 보니 한국에서 많이 쓰진 않는 듯하다.

Crittercism은 또다른 풀스택 툴이다. Pinterest, adidas, NETFLIX, YAHOO, Linkedin, ebay 등이 사용한다. 심볼화된 크래시 로그와 대시보드를 살펴보자:

dashboard

한눈에 잘 들어오진 않는다. Crittercism은 “breadcrumbs”이라는 기능을 지원하는데, 로그를 남겨 크래시가 나기 전에 앱이 어떤 컨텍스트에 있었는지 알 수 있게 해준다:

breadcrumbs

단, breadcrumbs는 유료 enterprise 계정에서 지원하는 기능이다.

또한 Crittercism은 크래시 로그가 어느 지역에서 발생하였는지 볼 수 있는 기능을 지원한다:

mapping

단 이 기능은 유저에게 위치정보 허가를 요청하지 않는다. 사용에 있어 조심하자.

Crittercism에는 독특한 가격 플랜이 있는데 앱이 일반 사용자를 위한 앱이냐 아니면 기업을 위한 앱이냐(B2C or B2B) 에 따라 가격 플랜이 달라진다.

Crittercism은 다양한 플랫폼을 지원하지만 사용의 편의성에 있어 조금 떨어진다.

Bugsense

Bugsense를 검색하면 Bugsense는 안 나오고 splunk mint라는 게 나온다. splunk에 인수된 건지, 이름을 바꾼 건지는 잘 모르겠지만 아무튼 현재는 splunk mint 라는 이름으로 서비스하고 있다. 재미있는 점은 splunk korea가 있다는 점이며, splunk로 검색하면 꽤 많은 자료가 나오는 것으로 보아 꽤 많이 쓰이고 있는 것 같다. splunk는 통합적인 데이터 관리 툴 및 플랫폼을 서비스하며, splunk mint는 그중에서도 모바일 툴이다.

Pricing plan. splunk mint 또한 MAU에 기반하여 가격이 책정되는데, 자세한 건 홈페이지에도 나와 있지 않고 직접 문의하라고 적혀 있다 -_-

아무튼 원문은 Bugsense기준으로 작성되었으니 이하 Bugsense로 작성하도록 한다.

Bugsense도 풀스택 서비스를 지원하고, 삼성, 인텔, 그루폰 등의 대기업에서 사용한다(다만 그루폰은 이것저것 다 쓰는 건지 어딜 가나 고객 목록에 그루폰이 있다). Crittercism처럼 다양한 플랫폼을 지원한다.

초기 셋업은 다른 플랫폼과 유사하게 계정을 만들고, SDK를 받아서, 프로젝트에 설치하고 API key를 세팅하면 된다. 대시보드는 이렇게 생겼다:

dashboard

로그를 선택하면 자세히 볼 수 있다:

crash log

어느 함수에서 어떤 에러를 발생시켰는지 정확한 코드 위치와 함께 볼 수 있다.

Bugsense의 한 가지 단점은, 서버사이드 심볼화를 하기 위해선 수동으로 dSYM파일을 업로드 해야 한다는 점이다. 클라이언트에서 심볼화를 할 수도 있지만, 이렇게 하면 크래시 코드 라인과 같은 세부 정보를 알 수 없다.

Bugsense 또한 어플리케이션의 컨텍스트를 추적할 수 있는 breadcrumbs를 제공하며, 버그를 수정했을 경우 해당 버그를 발생시킨 유저에게 푸쉬를 보낼 수 있는 “Fix notifications” 서비스 또한 재밌는 기능이다.

TestFlight (무료)

위 링크를 눌러보면 애플 홈페이지로 이동한다! 애플에 인수되었다. 당연히, 공짜다.

TestFlight는 베타버전의 배포를 위해 탄생했다. 버전이 올라가면서 액션 로깅이나 크래시 리포팅 기능 등이 추가되었다. TestFlight는 Adobe, Instagram, tumblr 등이 배포(over-the-air deployment), 트래킹, 크래시 리포팅을 위해 사용한다. 애플에 인수되었다길래 iOS 용만 있는 줄 알았는데 Android 용도 있다고 한다.

“.ipa” 파일을 서버에 업로드해서 배포할 수 있다. 서버사이드 대시보드는 아래와 같이 생겼다:

dashboard

대시보드의 왼쪽 메뉴에서 세션, 유저 피드백, 체크포인트 크래시(breadcrumbs와 유사) 등을 확인할 수 있다.

crash report

위에서 언급했다시피, 유저의 액션을 로깅할 수 있다. (원문 글 작성 시점인 2년 전에는) 써드파티 앱과의 연동이 불가능하다. 반면 베타 버전 배포와 크래시 리포트, 액션 로깅 등이 하나로 전부 가능하다는 점은 매력적이다.

HockeyApp

14년 12월에 마이크로소프트가 인수. Crashlytics와는 달리 그렇다고 해서 무료로 전환되진 않았다. Pricing plan. 비지니스 플랜으론 3 Owners / 15 Apps가 매달 30달러, 퍼스널 플랜으론 5 Apps가 매달 10 달러.

HockeyApp은 인디 개발자 세계에서는 잘 알려져 있다(인디 개발자가 만들었다고 한다). TestFlight처럼, 크래시 리포팅 외에도 베타버전 배포 관리 기능도 제공한다.

HockeyApp 셋업은 다른 프레임웤들과 동일하지만, 전체 소스를 임베딩하는 방법도 있다. 혹시 버그가 있을 경우에 직접 고치는 게 가능하다! (HockeyApp은 Quincykit이라는 오픈소스의 호스팅 버전인 듯 하다)

dashboard

대시보드 상단에 메뉴가 있고, 버전별 정보를 요약해서 보여주고 하단에는 통계 그래프가 있다. 데스크탑 앱은 Xcode에서 아카이브가 끝나면 새로운 빌드와 dSYM을 업로드한다. 한 번 직접 dSYM 파일을 서버에 업로드하면, Crashes tab을 선택할 수 있고 로그를 눌러 심볼화된 크래시 로그를 볼 수 있다:

crash log

HockeyApp 백엔드는 로그 서치 기능을 제공하는데, “show all of the crashes that happened on iOS6 but not on an iPad” 과 같은 걸 할 수 있다.

만약 직접 서버사이드 호스팅이 가능하다면, QuincyKit을 고려하자. HockeyApp은 QuincyKit을 호스팅하는 버전이다.

Summary and Comparison Chart

summary and comparison

2년 전 버전이니 참고만 하자.

The Bottom Line (최종 결과)

원문 저자의 의견으로는, Crashlytics가 최고의 크래시 리포팅 툴이다. 공짜이고, 사용하기에 편리하며, 리포팅 프로세스가 전부 자동화 되어 있어 dSYM파일을 직접 업로드 할 필요가 없다. 단점이 있다면 앱 배포 기능을 제공하지 않는다는 점이다.

만약 크로스 플랫폼 서비스를 찾는다면, Bugsense (splunk mint) 를 추천한다. 대쉬보드가 훌륭하다. 단, 저렴한 티어에서는 데이터가 오래 저장되지 않는다 (7일에서 30일 정도).

In my case

아마 Crashlytics를 기본적으로 사용하고 TestFlight를 고려할 것 같다. 어차피 베타버전 배포 및 유저 행동 로깅 또한 필요하기 때문에 Crashlytics만으론 해결이 안 된다. TestFlight를 좀 더 알아보고 결정할 예정이다.

Getting started에 해당하는 파트 2는 따로 번역하지 않을 예정이다. 필요하면 원문을 참고하자. 어떤 툴을 사용할 지 결정했다면 해당 툴로 검색하여 사용법을 찾아보는 것이 낫지 않을까 싶다.

TestFlight vs. Crashlytics vs. HockeyApp 에 따르면, 크래시 리포팅에는 Crashlytics가 좋고, 베타버전 배포에는 TestFlight가 좋아서 얘네는 둘 다 쓰기로 했다고 한다(Crashlytics도 베타버전 배포를 지원하는 모양이다). 나도 그런 방향으로 진행하기로 결정했다. 아, 저 글에서 HockeyApp은 유료라서 바로 비교에서 배제하고 있다 -_-;

'iOS' 카테고리의 다른 글

HitTesting  (0) 2015.08.05
얼굴 인식 및 사진 똑바로 세우기  (0) 2015.07.23
iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31
iOS Library: FastImageCache  (0) 2015.02.22
CALayer: anchorPoint  (0) 2015.02.21

개발한 앱 동영상 녹화시 화면 터치 표시하기

안드로이드는 설정에서 터치 표시 기능을 제공하지만, 아이폰은 그런 기능이 없다.

이를 대체하기 위한 오픈소스를 소개한다.

https://github.com/mapbox/Fingertips

// AppDelegate.h
#import "MBFingerTipWindow.h"

@property (strong, nonatomic) MBFingerTipWindow *window;

// AppDelegate.m
self.window = [[MBFingerTipWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.alwaysShowTouches = YES;

이것만 해 주면 터치를 표시할 수 있으며, fillColor, strokeColor, fadeDuration 등등 디테일한 조정도 가능하다. 아마 스토리보드를 쓴다면 window를 위와 같이 programmatical하게 만들지 않으므로 스토리보드에서 조정해야 할 것이다.

p.s. 아이폰 화면 녹화 방법은 Yosemite 에서 아이폰 화면 녹화 하기를 참고하자.

'iOS' 카테고리의 다른 글

얼굴 인식 및 사진 똑바로 세우기  (0) 2015.07.23
iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31
iOS Library: FastImageCache  (0) 2015.02.22
CALayer: anchorPoint  (0) 2015.02.21
Easing function  (0) 2015.02.15

iOS Library: FastImageCache

iOS Library: FastImageCache

패스트 이미지 캐쉬는 Path에서 공개한 라이브러리로써 빠른 이미지 로드를 돕는다. 이미지를 Uncompressed 상태로 저장하기 때문에 작은 크기의 이미지에 효율적이다.

자세한 사용법은 위 링크를 참조하자. 여기서는 개념만 소개한다. 위 링크 일부의 번역에 해당.

참고: 벤치마크 결과
iOS image caching. Libraries benchmark (SDWebImage vs FastImageCache)
disk에서 이미지를 불러올 때 탁월하게 빠른 성능을 보여준다.

패스트 이미지 캐쉬는 뭘 할까?

  • 비슷한 사이즈/스타일의 이미지들을 함께 저장
  • 디스크에 데이터 보존
  • 기존 방법보다 빠르게 이미지를 반환
  • 이미지의 사용 시기에 따라 자동 캐쉬 삭제 (쓴지 오래 된 건 자동으로 지움)
  • 이미지의 저장 및 로드에 대해 Model-based approach
  • 이미지가 캐쉬에 저장되기 전에 모델별 프로세싱 가능 (이해 안 감)

패스트 이미지 캐쉬는 어떻게 작동할까?

패스트 이미지 캐쉬를 이해하기 위해, 기존의 이미지를 다루는 방법들이 어떠한가를 먼저 알 필요가 있다.

The Scenario

API를 통해 이미지를 불러오고, 해당 이미지의 사이즈와 스타일을 원하는 형태로 처리한 후 디바이스에 저장한다. 이후에 어플리케이션이 이 이미지가 필요하면, 디스크로부터 메모리로 이미지를 로드하고 이미지뷰 등을 통해 화면에 렌더링한다.

The Problem

자 그럼 여기서 문제가 발생한다. 저장되어 있는, 즉 압축된 디스크상의 이미지 데이터를 유저가 볼 수 있도록 코어 애니메이션 레이어로 렌더링하는 작업이 매우 비싸다. 여기에 스크롤뷰까지 추가되면 컨텐츠(이미지)들이 빠르게 변하기 때문에 문제는 더 심각해진다. 아무튼, 부드러움을 유지하려면 60FPS를 유지해야 하기 때문에.

디스크로부터 이미지를 로드해서 스크린에 보여주는 과정은 다음과 같다:

  1. +[UIImage imageWithContentsOfFile:]Image I/O 를 사용해서 memory-mapped data로부터 CGimageRef를 만든다. 이 시점에서, 이미지는 아직 decoded되지 않았다.
  2. 반환받은 이미지를 UIImageView에 할당한다.
  3. An implicit CATransaction captures these layer tree modifications. (이해 안 감. 아무튼 내포된 CATransaction이 무언가 처리를 한다는 듯)
  4. 이후에 Core Animation이 내포된 트랜잭션을 처리한다. 이 트랜잭션은 레이어에 셋팅된 이미지의 카피를 만드는 것을 포함한다. 이미지를 카피하는 것은 아래 스텝의 전부 또는 일부로 구성된다:
    i. file IO와 decompression을 위한 버퍼가 할당된다.
    ii. 디스크로부터 파일을 읽어 메모리에 로드한다.
    iii. 압축된 이미지를 무압축 비트맵 형태로 decode한다. 이는 매우 CPU 바운드 작업이다.
    iv. 코어 애니메이션이 무압축 비트맵 데이터를 사용해서 레이어에 렌더링한다.

이는 매우 느릴 수 있다!

The Solution

패스트 이미지 캐쉬는 위 과정중 많은 부분을 생략할 수 있다. 다양한 테크닉을 사용해서:

Mapped Memory

패스트 이미지 캐쉬의 핵심은 이미지 테이블이다. 이미지 테이블은 2D 게임에서 사용되는 sprite sheets와 같다. 이미지 테이블은 같은 차원의 이미지들을 한 파일에 넣는다. 이 파일은 어플리케이션이 시작해서 종료될때까지 I/O를 위해 항상 열려있게 된다.

이미지 테이블은 mmap 시스템 콜(system call)을 사용한다. 이 시스템 콜은 파일 데이터를 메모리로 다이렉트로 매핑한다. 이 시스템 콜은 memcpy와는 다르게 단순히 디스크상의 데이터와 메모리 공간을 매핑한다.

이미지 캐쉬로 리퀘스트가 들어오면, 이미지 테이블은 상수시간에 해당 이미지의 위치를 찾아 메모리로 매핑한다. 그러면 backing store로 매핑된 파일 데이터를 갖는 CGImageRef가 만들어진다.

이러한 형태를 mapped memory라고 하는데, 이 mapped memory는 다양한 장점을 갖고 있다. 먼저 iOS의 버추얼 메모리 시스템이 파일 단위로 페이징하기 때문에(의역, 오역 가능성 있음), VM 시스템이 알아서 메모리를 관리해준다. 또한 mapped memory는 어플리케이션이 실제로 사용하는 메모리에 포함되지 않는다.

비슷한 방식으로, 이미지 데이터가 이미지 테이블에 저장되면 memory-mapped bitmap context가 만들어진다. 오리지날 이미지와 이 컨텍스트는 함께 하나의 엔티티 오브젝트로 이미지 테이블로 전달된다. 이 오브젝트는 컨텍스트에 바로 뿌릴 수도 있고, 편집도 가능하다.

Uncompressed Image Data

decompression 작업은 비싸기 때문에, 이미지 테이블은 무압축 이미지 데이터를 파일에 저장한다. 이는 퍼포먼스 향상 뿐만 아니라 utilize image format families도 가능케 한다.

이 방식의 문제 또한 명확한데, 당연하게도 디스크 용량을 더 많이 잡아먹는다. 특히 JPEG같은 포멧에 대해서는 더더욱. 이런 이유로 패스트 이미지 캐시는 작은 크기의 이미지에 대해서 좋은 성능을 보인다.

Byte Alignment

고성능 스크롤링을 위해서, 코어 애니메이션이 이미지를 카피하지 않고 직접 사용할 수 있도록 하는 것은 중요하다. 코어 애니메이션이 이미지를 카피하는 이유 중 하나는 CGImageRef의 부적합한 byte-alignment에 있다. 이미지 테이블은 각각의 이미지에 맞게 적합한 byte-alignment를 설정한다. 결과적으로 이미지를 로드하면 코어 애니메이션이 추가작업 없이 바로 쓸 수 있다!

고려사항들

Image Table Size

이미지 테이블이 가질 수 있는 최대 이미지 개수는 이미지 포멧1에 의해 결정된다. 이미지 테이블은 픽셀당 4바이트를 할당하므로 이미지 테이블 파일이 차지하는 최대 용량은 다음과 같다:

픽셀당 4바이트 * 이미지의 픽셀 수 * 이미지 테이블의 최대 이미지 개수

패스트 이미지 캐쉬를 사용하는 앱은 각 이미지 테이블이 몇개의 이미지를 가질 것인가를 신중히 고려해야 한다. 이미 꽉 찬 이미지 테이블에 새로운 이미지를 저장하려 한다면, least-recently-accessed2 이미지에 덮어씌운다.

Image Table Transience(일시적임)

이미지 테이블 파일은 유저의 caches 디렉토리 안의 ImageTables라는 서브디렉토리에 저장된다. iOS는 디스크 공간을 확보하기 위해 캐쉬 파일을 언제든 지울 수 있고, 따라서 패스트 이미지 캐쉬를 사용하는 앱은 이 점을 염두에 두고 이미지 테이블 파일이 사라졌을 때를 대비해야 한다.

Note: 유저의 caches 디렉토리 안의 파일들은 iTunes나 iCloud로 백업되지 않는다

Source Image Persistence

패스트 이미지 캐쉬는 오리지널 이미지를 이미지 데이터로 가공하여 이미지 테이블에 저장한다. 즉, 오리지널 이미지는 따로 보존하지 않는다.

예를 들어, 오리지널 이미지로 섬네일을 만들고 이를 이미지 테이블에 저장한다고 하자. 이 때 오리지널 이미지를 후에 다시 사용할 수 있도록 보존할 책임은 어플리케이션에게, 즉 우리에게 있다.

싱글 소스 이미지를 효율적으로 사용하기 위해서 이미지 포멧 패밀리가 명시될 수 있다. Working with Image Format Families를 참고하자. 이미지 포멧을 명시함으로써 더 효율적인 이미지 관리가 가능하다는 의미인 것 같다.

Data Protection

iOS 4에서, data protection이 등장했다. 디바이스가 잠기거나 꺼지면, 디스크가 암호화된다. 그런데 iOS 7에서 백그라운드 모드가 등장했고, 디스크가 암호화된 상태에서 앱이 파일에 어세스하려고 하는 이슈가 발생한다.

패스트 이미지 캐쉬는 이미지 테이블 파일을 만들 때 각 이미지 포멧에 데이터 프로텍션 모드를 지정할 수 있다. 이미지 테이블 파일의 데이터 프로텍션을 키면 디스크가 암호화 되어 있을 때 이미지 데이터를 읽고 쓰지 못하게 된다 (이해 잘 안감).

Requirements

  • iOS 6.0 ~
  • use ARC
  • Demo requires Xcode 5.0 ~

Getting Started

일단 코코아팟으로 깔자. 직접 받아서 추가하는것도 썩 복잡하지 않은 듯.

Initial Configuration

앱의 실행마다 이미지 캐쉬를 사용하기 전에 설정이 필요하다. 앱델리게이트에 적당히 배치하자.

Creating Image Formats

각 이미지 포멧은 이미지 캐쉬에서 사용할 이미지 테이블과 일치한다(?). 이미지 테이블에 저장하는, 즉 화면에 렌더링하는 이미지를 만들기 위해 같은 오리지널 소스 이미지를 사용하는 이미지 포멧은 같은 이미지 포멧 패밀리에 속한다. 즉, 오리지널 이미지가 하나 있고 이걸 리사이징 해서 미디움 사이즈의 썸네일과 스몰 사이즈의 썸네일을 만든다면 이 두 썸네일은 같은 이미지 포멧 패밀리에 해당한다.

static NSString *XXImageFormatNameUserThumbnailSmall = @"com.mycompany.myapp.XXImageFormatNameUserThumbnailSmall";
static NSString *XXImageFormatNameUserThumbnailMedium = @"com.mycompany.myapp.XXImageFormatNameUserThumbnailMedium";
static NSString *XXImageFormatFamilyUserThumbnails = @"com.mycompany.myapp.XXImageFormatFamilyUserThumbnails";

FICImageFormat *smallUserThumbnailImageFormat = [[FICImageFormat alloc] init];
smallUserThumbnailImageFormat.name = XXImageFormatNameUserThumbnailSmall;
smallUserThumbnailImageFormat.family = XXImageFormatFamilyUserThumbnails;
smallUserThumbnailImageFormat.style = FICImageFormatStyle16BitBGR;
smallUserThumbnailImageFormat.imageSize = CGSizeMake(50, 50);
smallUserThumbnailImageFormat.maximumCount = 250;
smallUserThumbnailImageFormat.devices = FICImageFormatDevicePhone;
smallUserThumbnailImageFormat.protectionMode = FICImageFormatProtectionModeNone;

FICImageFormat *mediumUserThumbnailImageFormat = [[FICImageFormat alloc] init];
mediumUserThumbnailImageFormat.name = XXImageFormatNameUserThumbnailMedium;
mediumUserThumbnailImageFormat.family = XXImageFormatFamilyUserThumbnails;
mediumUserThumbnailImageFormat.style = FICImageFormatStyle32BitBGRA;
mediumUserThumbnailImageFormat.imageSize = CGSizeMake(100, 100);
mediumUserThumbnailImageFormat.maximumCount = 250;
mediumUserThumbnailImageFormat.devices = FICImageFormatDevicePhone;
mediumUserThumbnailImageFormat.protectionMode = FICImageFormatProtectionModeNone;

NSArray *imageFormats = @[smallUserThumbnailImageFormat, mediumUserThumbnailImageFormat];

이미지 포멧의 스타일은 아래 4가지가 있다:

  • 32비트 컬러+알파 (default)
  • 32비트 컬러
  • 16비트 컬러
  • 8비트 흑백

소스 이미지에 투명이 없다면(jpeg같이) 32비트 컬러 with no alpha를 사용하면 코어 애니메이션의 퍼포먼스가 향상된다. 소스 이미지가 매우 작거나 컬러가 몇 개 없다면 16비트 컬러를 사용할 수 있다.

Configuring the Image Cache

이미지 포멧이 정의되면, 이미지 캐쉬에 할당해야 한다. 델리게이트를 설저아고 이미지 포멧만 넣으면 된다.

FICImageCache *sharedImageCache = [FICImageCache sharedImageCache];
sharedImageCache.delegate = self;
sharedImageCache.formats = imageFormats;

Creating Entities

엔티티는 FICEntity 프로토콜을 따르는 오브젝트다. 엔티티들은 각각 이미지 테이블의 이미지를 정의하고, 이미지 캐쉬에 저장되어 있는 이미지를 화면에 렌더링한다.

사용법이 꽤나 복잡해 보여, 이것보다는 SDWebImage를 고려하기로 하고 이 포스트는 이쯤에서 정리한다. 실제 적용하려면 원문의 튜토리얼을 따라가도록 하자.


  1. png, jpg 같은 이미지 포멧이 아니라 앱 내부적으로 사용하는 포멧을 의미

  2. recently, least-accessed로 이해하면 편하다. 적게 사용된 이미지를 버린다는 건데 이 때 최근 사용 기록만 보겠다는 것.

'iOS' 카테고리의 다른 글

iOS Crash Reporting Tools 소개  (1) 2015.07.20
개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31
iOS Library: FastImageCache  (0) 2015.02.22
CALayer: anchorPoint  (0) 2015.02.21
Easing function  (0) 2015.02.15
UIScrollView touch event  (2) 2015.02.15

CALayer: anchorPoint

CALayer: anchorPoint

CA는 Core Animation. 이 글은 스택오버플로 http://stackoverflow.com/questions/12208361/why-does-my-view-move-when-i-set-its-frame-after-changing-its-anchorpoint/12208587#12208587 답변의 번역이다.

answer

CALayer가 수퍼레이어로부터 어떻게 나타나는지를 결정하는 네가지 프로퍼티가 있다:

  • position (anchorPoint의 포지션)
  • bounds (정확히는 boundssize파트만)
  • anchorPoint
  • transform

보다시피 frame은 이 프로퍼티에 포함되지 않고, 위 프로퍼티로들로부터 만들어진다.

(100, 100, 100, 20)의 프레임을 갖는 UILabel을 만들었다고 하자. UILabelUIView의 서브클래스이며 모든 UIView는 레이어를 갖는다. 이 레이어는 디폴트 앵커 포인트로 (0.5, 0.5)를 가진다. 따라서 bounds는 (0, 0, 100, 20)이 되고 position은 (150, 110)이 된다.

1

여기서 anchorPoint를 (1,1)로 바꾸면 position은 그대로기 때문에 아래와 같이 변한다.

2

apply

http://stackoverflow.com/questions/14007983/scale-uiview-with-the-top-center-as-the-anchor-point

앵커 포인트를 조정하여 뷰의 중심을 고정시켜 놓고 scale animation을 적용할 수 있다.
아래는 뷰를 상단 중앙에 고정시키는 소스다:

CGRect frame = view.frame;
CGPoint topCenter = CGPointMake(CGRectGetMidX(frame), CGRectGetMinY(frame));

view.layer.anchorPoint = CGPointMake(0.5, 0);
view.layer.position = topCenter;

이후 scale 애니메이션을 적용하면 상단 중앙에 고정된 것을 확인할 수 있다.

'iOS' 카테고리의 다른 글

개발한 앱 동영상 녹화시 화면 터치 표시하기  (0) 2015.05.31
iOS Library: FastImageCache  (0) 2015.02.22
CALayer: anchorPoint  (0) 2015.02.21
Easing function  (0) 2015.02.15
UIScrollView touch event  (2) 2015.02.15
iOS 7/8 present modal view transparent  (1) 2015.02.09

Easing function

Easing function

Easing functions specify the rate of change of a parameter over time.
즉, 시간에 따른 속도의 변화를 나타내는 함수다.

썩 유명한 개념인 것 같은데, 나만 몰랐던 거 같다 -_-;
애니메이션을 만들고자 한다면 필수적으로 들어가는 개념이니 이번 기회에 잘 알아두도록 하자.

  • Ease In: 속도가 점점 증가
  • Ease Out: 속도가 점점 감소
  • Ease InOut: 속도가 점점 증가하다 다시 감소

Bezier curve

베지에 곡선. n개의 점으로부터 얻어지는 n-1차 곡선이다. 즉, 시작점과 끝점이 있고 중간에 2개의 조절점이 있다면 3차 베지에 곡선이다. 워드에서 곡선을 그릴 때 느낌인 듯. 아래 구성 애니메이션을 참고하자:
http://www.jasondavies.com/animated-bezier/
http://en.wikipedia.org/wiki/Bezier_Curve#Constructing_B.C3.A9zier_curves

quadratic bezier curve

Easing function은 이 베지에 곡선으로 만들어진다. 애플의 프로그래밍 가이드를 보면:

CAMediaTimingFunction *customTimingFunction;
customTimingFunction=[CAMediaTimingFunction functionWithControlPoints:0.25f :0.1f :0.25f :1.0f];

이렇게 코어 애니메이션에서 시작점과 끝점 (0, 0), (1.0, 1.0)외에 두개의 조절점 (0.25, 0.1), (0.25, 0.1)을 추가하여 베지에 곡선을 만든다. 코어 애니메이션으로 Easing function을 적용하는 예제는 여길 참고하자: http://lafosca.cat/smooth-and-custom-animations-in-ios/

Libraries

  • AHEasing: 가장 기본이 되는 Easing function library. 수학적 함수들과 그 래퍼로 구성됨.
  • UIView+EasingFunctions: 써보진 않았는데, UIView animation에서 AHEasing의 function들을 사용할 수 있도록 만들어 놓은 extension
  • SCScrollView: 스크롤뷰에서 contentOffset을 조정할 때 easing function을 사용할 수 있도록 한 라이브러리

참고

'iOS' 카테고리의 다른 글

iOS Library: FastImageCache  (0) 2015.02.22
CALayer: anchorPoint  (0) 2015.02.21
Easing function  (0) 2015.02.15
UIScrollView touch event  (2) 2015.02.15
iOS 7/8 present modal view transparent  (1) 2015.02.09
Auto Layout Tutorial  (0) 2015.01.15

UIScrollView touch event

예전에는, 스크롤뷰로부터 터치 이벤트를 캐치하기 위해 스크롤뷰를 상속받아서 touches- 메소드들을 오버라이딩 해 주어야 했다. 스크롤뷰가 터치 이벤트를 죄다 먹어버리기 때문이었다.

당시에 그 작업을 했던 게 iOS 4.3 을 지원하던 한 2-3년 전쯤이었던거 같다.


이번에 이 작업을 다시 해 보니, touches- 메소드가 터치 이벤트를 제대로 받아오지 못한다. 자세히 찾아보진 않았지만 스크롤뷰에서 터치 이벤트를 가져오는 방식이 touches- 메소드에서 제스처 기반으로 바뀐 것 같다. 이제는 터치 이벤트를 가져오려면 스크롤뷰를 오버라이딩 하지 말고 제스처를 붙이고, 동시에 여러 제스처를 인식하도록 델리게이트를 설정해주면 된다.


참고: http://stackoverflow.com/questions/13736399/intercepting-pan-gestures-over-a-uiscrollview-breaks-scrolling


p.s: https://www.cocoanetics.com/2010/06/hacking-uiscrollview-gesture-recognizers/ 이런걸 보면 예전에 바뀐 거 같은데 왜 내가 전에 작업할땐 그랬고 최근 스택오버플로 답변들도 스크롤뷰를 상속받으라고 하는지는 잘 모르겠다. 알게 되면 추가함.

'iOS' 카테고리의 다른 글

CALayer: anchorPoint  (0) 2015.02.21
Easing function  (0) 2015.02.15
UIScrollView touch event  (2) 2015.02.15
iOS 7/8 present modal view transparent  (1) 2015.02.09
Auto Layout Tutorial  (0) 2015.01.15
아이폰 6, 6+, iOS 8으로의 포팅: 10가지 팁  (0) 2015.01.03