[iPhone] 아이폰 앱 디자인 실수: 문맥에 대한 간과

아이폰 2013. 2. 16. 02:16



Smashing Magazine 사이트에 아이폰앱 디자인시 범하는 실수에 대한 글이 있어 번역해보았습니다. 좋은 내용이네요 ^^b

(발번역이지만 이해해 주세요 ^^) 

원문: iPhone Apps Design Mistakes  : Disregard Of Context by Alex Komarov




아이폰 앱들의 디자인 실수  : 컨텍스트를 간과한다. 

 - 아이폰은 훨씬 큰 그림의 일부일 뿐이다. 인간과 환경요소들 얼마나 잘 풀어내는냐가 앱의 성공요소를 결정한다.  

    너무나 자주 아이폰 개발자들은 고객과는 동떨어진 제품을 만들어낸다. 

- 정말로 호소력있는 앱을 창조하기 위해서는, 앱의 메커니즘에만 집중하는 행위를 멈춰라. 멀리떨어져서 보아라. 

- 사람을 감싸고 있는 복잡한 환경적인 요소들 뿐만 아니라 애플리케이션을 사용하는 사람들을 이해하라. 

- 이러한 디자인 과제들의 문맥을 더 잘 이해하기 위해서 우리는 몇몇 수준에서 인간과 환경적인 요소들에 주목할 것이다. 


레벨1, 당신은 고객들이 사랑하는 앱을 만들기 위해 여기에 있다. 

         멀리 떨어져서 보아라


레벨1 - 앱자체

- 많은 개발자들이 그들의 앱을 바라보는 관점이다. 당신의 앱이 어떻게 보여야 하고,  왜 고객들이 그것에 관심을 

   주목할 것인지에 대한 비전을 가지고 있다. 그러나  제품에만 몰두하여 바라본다면 잘못된 문맥 속에 놓고  

    잘못된 목적과 잘못된 사용자들을 위해  설계하게 될것이다. 

- 이것이 멀리 떨어져서 보아야 하는 이유이다. 


레벨2, 한명의 사람이 이 앱을 사용하고 있다. 

- 그 사용자는 특정한 목적과 과제를 가지고 있다. : 이 부분에서 가장 두드러지고 가장 무시되는 몇가지에 대해서 알아볼것이다.

- 아이폰에 존재하는 인간적인 요소들. 기본적인 인체공학과 시각적인 한계, 공통적인 디자인 실수에 대해서 이야기 할 것이다. 



레벨 3. 그 사람은 특정한 환경속에서 이 앱을 사용하고 있다.

- 뒤로 물러나라. 그러면 이 앱은 복잡한 사회 환경속에 일부라는 것을 보게 될것이다. 앱은 사람들 사이의 커뮤니케이션하고,

   사람들이 더 큰 목적을 달성하는 것을 돕는 데에 있어서 상대적으로 적은 역할을 맡고 있다.

- 이곳이 사회적 컴포넌들이 동작하는 장소이다. 네트워킹과 커뮤니티, 소셜 기반의 웹사이트와 같은…

- 애플리케이션들과 많은 다른 것들이 환경과 문맥을 창조하는…그 환경속에서 애플리케이션들은 사용되어질 것이다. 



레벨4. 환경은 더 큰 문화의 일부이다.

- 다른 문화들간에 독특한 니즈를 해결하는 당신의 능력이 제품의 성공에 영향을 미칠것이다. 

  그것을 무시하는 것은 너무 비용이 크며, 만일 앱이 세계적으로 팔릴 것이라면 특히 더 그렇다. 

  환경은 글로벌 네트워킹의 일부임을 이해하는 것이 중요하다. 

- 지역문화 뿐만이 아니라 세계적인 성공을 얻을 앱을 창조하기 위해서는 문화적인 차이, 트렌드, 메타포를 인식해야 한다. 




레벨2, 사람의 니즈와 한계를 이해하라. 


"두번 측정하고, 한번 잘라라" 는 실제로도 효과적인 전략이다. 아이폰 개발자로서 한걸음 뒤로 물러나 코딩을 시작하기 전에 

아래와 같은 질문들에 답할 수 있어야 한다. 

- 누가 당신의 애플리케이션들을 사용할 것인가?

- 그 사용자의 기능요구사항은 무엇인가?

- 그 사람의 제한(한계)는 무엇인가?


이 질문에 대한 답들이 당신의 관점을 넓혀주고, 당신 고객의 니즈를 해결하는데 도움을 줄것이다. 

모든 인간적인 요소 전문인들은 단지 이것에 전념한다. 


기본적인 인체공학

- 아이폰과 관련하여 물리적, 인지적, 인체공하적인 진실들이 몇가지 있다. 


1. 우리의 손가락들은 마우스 포인터가 아니다. 

- 대다수의 탭가능한 객체들이 너무 작아서 사용하기를 좌절시키는 인터페이스를 사용한다. 

- 구글맵앱보다 2배 적은 핀들을 사용해서, 태핑이 매우 어렵다.(iFitness) 손가락이 3개 이상의 핀영역을 차지하기 때문에..

- 결국 영역을 반복해서 태핑하게 되고 랜덤으로 핀이 활성화되어 원치 않는 결과를 보게 된다. 


인체공학적인 문제들을 해결하기 위한 몇가지 방법이 있다.

1) 버튼과 다른 탭가능한 객체들을 더크게 만들어라.

2) 더크게 만드는 것이 불가능하다면 버튼 그 자체보다 더 크게 클릭 가능하게 영역을 확장해라

3) 각 화면에서 옵션의 수를 줄이고, 선택의 프로세스를 순차적이게 만들어라. 

4) 인터페이스 안에서 다중 터치 제스처를 구현해라. 

    예를 들자면,  두손가락 줌제스처를 사용한것이 머슬 그룹을 더 쉽게 선택할 수 있게 해줄것이다. 


2. 불행하게도 우리는 수퍼 영웅이 아니다. 

- 앱 디자이너들은 시각적인 제약사항들에 대해 고려해야 한다. 모바일 폰들은 일반적으로 컴퓨터보다 조명 조건이 

   좋지 않은 장소에서 사용되어진다. 

- 덜컹거리는 버스 기차에서, 햇빛 비추는 거리에서 걸으면서 앱을 사용하는 사람들을 생각해보아라. 

   기술이 유용하고, 완변하게 실행될 지라도 사람들은 무슨 일이 벌어지는지 알기가 힘들어진다면 앱 사용을 꺼리게 될것이다. 

- 여기 몇가지 시각적인 한계를 고려하지 않은 잠재적으로만 유용한 앱이 몇가지 있다.

   [너무 어무운 색만을 사용해서 앱을 디자인 하지 말아라.]

TweetDeck


여기 몇가지 실수를 피할수 있는 방법들이 있다. 

1) 반드시 필요한 요소들만 선택해라. 그것을 더 크게 만들고, 다른 모든것들은 제거하라. 

    만일 필요하다면 더 많은 옵션을 위해  추가적인 화면을 만들어라. [화면에 덜 필수적인 아이콘들은 제거하라.]


2) 아이폰에서의 픽셀 화면 크기는 컴퓨터 화면보다 작다는 것을 기억해라.  

    시뮬레이터에서 보는 스크린샷은 아이폰에서는  실제로 더 작다. 



레벨3, 사용자의 환경에 특화된 문제(과업)들을 이해하라. 


목적과 환경

- 당신의 앱은 사용자가 더 큰 목적 달성을 돕는데 상대적으로 더 적은 역할을 한다. 

-  사람들이 어떤 목적을 가지고 있고 그들이 그것들을 달성하기 위해 필요한 것을  더 잘 이해할 수록,

   당신은 그들의 니즈를 더 많이 만족하는 앱을 만들수 있다. 

- 모바일폰은 종종 시끄럽고 집중을 저해하는 환경속에서 사용된다. 

    도시를 통과하는 간단한 산책만 하더라도 시끄러운 요소들이 많다. 

- 다음 예제를 보아라. 어떤 목소리 메모앱이 더 역할을 잘 할 것 같은가? (Apple Voice vs  iTalk)

Apple Voice MemosiTalk




애플의 보이스 메모가 더 멋져 보이지만, iTalk가 평균적인 사용자들의 목적과 환경을 더 잘 해결하고 있다. 

생각해 보아라. 왜 사람들은 노트를 쓰는 것보다 목소리를 녹음하는 것을 더 선호하는가? 

오디오 형식은 단순한 텍스트보다 장점이 거의 없다. 검색을 할수도 없고, 텍스트 만큼 쉽게  수정과 보완을 할수 없다. 

대부분의 시나리오에서는 정보를 교환하는 관점에서 텍스트가 훨씬 더 편리한 형식이다. 


그렇다면 … 더 중요하게는 언제 사람들은 목소리 메모를 사용하는가? 언제 그들이 타이핑 할 수 없는가? 

가장 흔한 때는 운전하고 있을 때이다. 운전하면서 타이핑 하는 것이 그렇지 않는 경우보다 사고의 위험이 23배 높다고 한다. 

이러한 경우에 어떤 앱이 더 사용하기 쉬운가? 크고 빛나는 마이크와 작은 녹음 버튼을 가진 앱은 누르기 불편하지 않을까?

반면 화면 절반 크기의 녹음 버튼을 가진 것은? 당연히 후자이다. 


사용자가 녹음이 활성화 됐다는 것을 확인하게 하는 것도 역시 중요하다. 어떤 인터페이스가 디바이스 상태를 더 잘 알리고 있는가? 당신이 녹음을 완료하고 싶을 때는 어디를 택해야 하는가?


전박적으로 어떤 지다인이 더 잘 동작하는 가에 기초하면, iTalk의 승리이다.  애플 보이스 메모는 친구의 폰을 구경할때는

멋져보이지만 실제환경에서는 유용하지 않다. 



모바일 폰들, 네트워킹과 커뮤니티 

- 의심의 여지없이 모바일폰은 소셜 도구 중에 하나이다. 머 많은 사람들을 관련시킬수록 더 많은 경험들이 발생한다. 

    생각해보아라. 오직 하나의 폰만 가지고 있다면, 별로 유용하지 않을 것이다. 유투브, 페이스북, 트위터는 우리들은 사회적인

    존재임을 이해하고 만들어진 서비스이다.

- 우리는 공유하기를 원한다. 사회적인 인터렉션을 위탁한 설계가 얼마나 극적으로 모바일 세계를 변화시키는지 상상해보아라. 


외견상으로 보이는 정보를 공유하고 획득하는 반복적인 방법들에 사람들은 압도당한다. 그것들에 대처하기 위해서 디자이너들은 

애플리케이션을 가능한한 효과적으로 만들기 위해 아이폰 플랫폼을 잘 이해해야 한다. 

Bump : 단순히 부딪히는 제스터를  통해 연락처 정보를 교환함. 

Mover : 쉽게 정보(사진?)을 전달 할 수 있음.

Loopt : 내 주변에 있는 사용자들을 알 수 있음. 

BumpMoverLoopt




레벨4. 환경은 거대한 문화의 일부이다.


다른 문화간의 독특한 니즈를 해결하는 당신의 능력이 당신 앱의 성공에 영향을 미칠 것이다.  그것들을 무시하는 것은 비용이 

너무 크고, 특히 앱을 세계를 대상으로 판매할 경우 더욱 그렇다. 지다인은 지역적인 문제들에 적응되야 한다. 


사용성 전문가 제이콥 닐슨은 이렇게 이야기 했다. 

"ATM 기 전에는 왜 그렇게 큰 버튼을 가졌는지 이해하지 못했다. 2월의 스톡홀름에서 즉시 이해할 수 있었다. 

두꺼운 장갑을 끼고도 누르는 것이 가능하다."


시스템은 사용자의 문화적인 특성에 부합해야 한다. 이것은 공격적인 아이콘을 피하는 것 이상의 의미를 갖는다. 

비지니스가 수행되는 방식을 수용하고, 다양한 나라에서 사람들이 소통하는 방식을 이해해야 한다. 



결론

훌륭한 앱을 디자인 하는것은 쉬운 일이 아니다. 

하지만 사용자의 니즈를 이해하려는 당신은 노력은 보상받을 것이다. 




[iPhone] iOS6 회전 지원하기

아이폰 2013. 1. 25. 01:17



iOS6.0 부터 회전을 지원하는 delegate 메소드가 조금 변경되었습니다.


iOS6.0 업데이트 후 회전이 올바로 동작하지 않는 것을 보고 적잖이 당황했었습니다. 


관련내용을 찾아보니 의외로 어렵지 않게 6.0에서도 회전에 대응할 수 있었습니다~ 


그럼 6.0 이상 및 하위버전에서 회전을 올바로 지원하기 위한방법에 대해서 알아봅시다





홈 버튼을 기준으로 왼쪽으로 회전, 오른쪽으로 회전, 거꾸로 뒤집었을 때 등 각 방향을 지칭하게 됩니다!








먼저 기존에는 회전을 지원하기 위해 아래와 같은 스텝을 거쳤습니다.



1. Window에 View 추가 


[self.window addSubview:viewController.view]




2. UIViewController 에 지원 회전방향 delegate 구현 


// 자동회전 지원하는 방향 
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation == UIInterfaceOrientationPortrait || 
               interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||            
               interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}



3. UIViewController 에서 회전하기 전/후 호출되는 delegate 구현 


- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    // 회전하기 전에 호출됩니다.
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
   // 회전 후에 호출됩니다.
}



shouldAutorotateToInterfaceOrientation의 UIInterfaceOrentation 파라미터 타입은 다음과 같이 

enum 타입 상수로  선언되어 있습니다.


typedef NS_ENUM(NSInteger, UIInterfaceOrientation) {
    UIInterfaceOrientationPortrait           = UIDeviceOrientationPortrait,
    UIInterfaceOrientationPortraitUpsideDown = UIDeviceOrientationPortraitUpsideDown,
    UIInterfaceOrientationLandscapeLeft      = UIDeviceOrientationLandscapeRight,
    UIInterfaceOrientationLandscapeRight     = UIDeviceOrientationLandscapeLeft
};




iOS 6.0 에서는 무엇을 바뀌었을까요?



iOS6.0에서는 1번과 2번 스텝이 변경되었습니다. 




1. Window에 rootViewController 추가 


self.window.rootViewController = viewController;


기존에는 Window의 View를 추가했지만, iOS6.0 부터는 반드시 Window의 rootViewController로 지정해야 합니다. 





2. UIViewController 에 지원 회전방향 delegate 구현 


//  자동회전 지원 유무 
- (BOOL) shouldAutorotate {
      return YES; 
}
// 지원하는 회전방향 
- (NSUInteger)supportedInterfaceOrientations {
    return (1 << UIInterfaceOrientationPortrait) | (1 << UIInterfaceOrientationLandscapeLeft) | (1 << UIInterfaceOrientationLandscapeRight);
}




기존에 2가지 역할을 하던 메소드가 자동회전 유무와 회전하는 방향을 반환하는 메소드로 각각 분리되었습니다.


또 지원하는 회전방향의 경우,  NSUInterger 반환값으로 변경되었는데 이 값은 지원하는 회전방향의 비트가 셋팅된


비트 플래그 값입니다.


typedef NS_OPTIONS(NSUInteger, UIInterfaceOrientationMask) {
    UIInterfaceOrientationMaskPortrait = (1 << UIInterfaceOrientationPortrait),
    UIInterfaceOrientationMaskLandscapeLeft = (1 << UIInterfaceOrientationLandscapeLeft),
    UIInterfaceOrientationMaskLandscapeRight = (1 << UIInterfaceOrientationLandscapeRight),
    UIInterfaceOrientationMaskPortraitUpsideDown = (1 << UIInterfaceOrientationPortraitUpsideDown),
    UIInterfaceOrientationMaskLandscape = (UIInterfaceOrientationMaskLandscapeLeft | UIInterfaceOrientationMaskLandscapeRight),
    UIInterfaceOrientationMaskAll = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft |
                                                   UIInterfaceOrientationMaskLandscapeRight | UIInterfaceOrientationMaskPortraitUpsideDown),
    UIInterfaceOrientationMaskAllButUpsideDown = (UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskLandscapeLeft | 
                                                                         UIInterfaceOrientationMaskLandscapeRight),
};



1을 UIInterfaceOrentation 상수값 만큼 왼쪽으로 시프트해주면, 해당하는 방향의 비트를 셋팅할 수 있지요.



또 한가지 중요한 사항은, 커스텀 UINavigationController와 UITabBarController를 사용한 경우(상속해서)


이 커스텀 클래스의 회전 지원 delegate 메소드에서는 topViewController의 회전지원 delegate 메소드로 


위임해야 한다는 것입니다. 아래와 같이 말이죠!



...
- (BOOL) shouldAutorotate {
    return [topViewController shouldAutorotate];
}
...







다음은 iOS6.0 이상 및 미만에서 회전을 올바로 처리하기 위한 코드를 정리한 것입니다.



- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    ...
    // iOS6.0 이상
    NSString *currentVersion = [[UIDevice currentDevice] systemVersion];
    
    NSLog(@"%@", currentVersion);
    
    if ([currentVersion compare:@"6.0" options:NSNumericSearch] != NSOrderedAscending) {
        self.window.rootViewController = self.viewController;
    } else {
        // 하위버전
        [self.window addSubview:self.viewController.view];
    }
    [self.window makeKeyAndVisible];
    return YES;
}

#pragma mark - 회전지원 iOS6.0 이상

// 자동회전 지원유무
- (BOOL) shouldAutorotate {
    return YES;
}

// 회전방향 지원유무
- (NSUInteger)supportedInterfaceOrientations {
    return (1 << UIInterfaceOrientationPortrait) |
           (1 << UIInterfaceOrientationPortraitUpsideDown) |
           (1 << UIInterfaceOrientationLandscapeLeft) |
           (1 << UIInterfaceOrientationLandscapeRight);
}

#pragma mark - 회전지원 iOS6.0 미만 

// 회전방향 지원유무
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
    return (interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
            interfaceOrientation == UIInterfaceOrientationLandscapeLeft ||
            interfaceOrientation == UIInterfaceOrientationLandscapeRight ||
            interfaceOrientation == UIInterfaceOrientationPortraitUpsideDown
            );
}


#pragma mark - 회전 델리게이트 

// 공통
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
    // 회전하기 전에 호출됩니다.
}

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
    // 회전 후에 호출됩니다.
}




소스코드 다운로드

Rotation.zip





[iPhone] UIView 애니메이션과 코어 애니메이션(CoreAnimation)

아이폰 2013. 1. 23. 01:43



아이폰에서는 애니메이션을 쉽게 구현할 수 있는 수단들을 제공한다


UIView의 위치 이동, 크기변경, 회전, 페이드인/아웃 등과 간단한 애니메이션은 UIView 애니메이션을


사용해서 구현할 수 있다. UIView 클래스에 애니메이션을 위한 메서드들이 존재하며, 


iOS 4.0 부터는 블럭방식의 애니메이션 메소드를 제공해서 코드단에서 편리하게 구현할 수 있다. 


CoreAnimation은 CALayer 단에 적용되며, UIView 애니메이션 효과를 구현할 수 있을 뿐만 아니라  


좀더 복잡하고 세밀한 애니메이션 효과를 줄 수 있다.  하지만 UIView 애니메이션보다는 코딩량이 좀더 많고 번거롭다 


간략히 UIView 애니메이션과 코어 애니메이션을 사용하는 방법에 대해서 알아보자


다음은 UIView를 사용하여 구현한 푸시 애니메이션(네비게이션 푸시가 아니라, 버튼이 움푹 눌리는 듯한 효과)과 


CoreAnimation을 사용하여 구현한 플립 애니메이션(y축을 기준으로 뒤집어 지는 듯한 효과)이다 




1. UIView 애니메이션을 사용한 푸시효과




구현방식은 터치가 발생하면 x, y축 스케일(scale)을 0.3초 동안 0.9로 축소하고


다시 0.3 초 동안 1.0으로 되돌아 오는 효과를 적용했다. UI 요소가 눌리는 듯한 효과를 줄 수 있으며, 인터렉티브한 


느낌을 줄 수 있어서 자주 사용하는 효과중에 하나이다. 


 [UIView animateWithDuration:0.3 animations:^(void) {
        self.transform = CGAffineTransformMakeScale(0.9, 0.9);
    } completion:^(BOOL finished) {
        [UIView animateWithDuration:0.3 animations:^(void) {
            self.transform = CGAffineTransformIdentity;
        }];
    }];


animations 블럭에서 속성을 변경하는 코드가 비동기 방식으로 (메인쓰레드에서) 애니메이션되는 방식이다. 


위 코드는 가로, 세로 Scale을 각각 0.9로 변환하는 애니메이션을 0.3초 동안 수행하고, 


애니메이션이 완료(completion) 하면 원래 스케일로 복원하는 애니메이션을 다시 0.3초 동안 수행한다. 


CGAffineTransform 즉 아핀변환은 벡터에서 등장하는 용어로 변환을 행렬을 사용해서 나타낸 것이라는 정도만 알아두자.





2. CoreAnimation을 사용한 플립효과 




CoreAnimation 은 UIView가 아닌 CALayer에 적용되며, 애니메이션할 CALayer의 property를  지정하고 


애니메이션 상태를 좀더 세밀하게 제어할 수 있는 속성을 제공한다. 


플립 애니메이션을 구현하는 동안 주의할 점은  y축을 기준으로 회전을 하면 이미지가 좌우가 뒤집어 지는 


현상이 발생한다는 것이다.  레이어를 회전시켰기 때문에 뒤집어 지는 것이다. 





이문제는 먼저 90도를 회전 시키고, -90로 뒤집은 상태에서 다시 나머지 회전을 수행하는 방식을


사용하면 해결 할 수 있다. 


- (void)flip {
    // 90도 회전 
    CABasicAnimation *rotateY = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    rotateY.beginTime = 0.0;
    rotateY.toValue = [NSNumber numberWithFloat:radians(90)];
    rotateY.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    rotateY.autoreverses = NO;
    rotateY.repeatCount = 1;
    rotateY.removedOnCompletion = NO;
    rotateY.duration = 0.4;
    rotateY.fillMode = kCAFillModeForwards;
    
    // -90도로 뒤집은 상태에서 나머지 회전
    CABasicAnimation *rotateY2 = [CABasicAnimation animationWithKeyPath:@"transform.rotation.y"];
    rotateY2.beginTime = 0.4;
    rotateY2.fromValue = [NSNumber numberWithFloat:radians(-90)];
    rotateY2.toValue = [NSNumber numberWithFloat:radians(0)];
    rotateY2.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    rotateY2.autoreverses = NO;
    rotateY2.repeatCount = 1;
    rotateY2.removedOnCompletion = NO;
    rotateY2.duration = 0.4;
    rotateY2.fillMode = kCAFillModeForwards;
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    group.animations = [NSArray arrayWithObjects:rotateY, rotateY2, nil];
    group.duration = 0.8;
    group.autoreverses = NO;
    group.repeatCount = 1;
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    group.delegate = self;
    
    [self.layer addAnimation:group forKey:nil];   
}

CAAnimationGroup은 동시에 여러 애니메이션을 복합적으로 적용할 때 사용하며,  CABasicAnimation의  


beginTime 속성을 사용해서 애니메이션 시작 시점을 조정할 수 있다. 총 0.8초 동안의 시간동안 처음 0.4초 동안


90도 회전하고, 0.4초 후 -90도 뒤집어진 상태에서 0도로 0.4초 동안 회전시켜 마치 카드가 자연스럽게 


뒤집어 지는 듯한 플립효과를 구현하였다


거의 대부분의 애니메이션은 UIView 애니메이션으로 구현할 수 있지만 


UI/UX에 특화된 좀더 재미나고 멋진 효과를 가진 앱을 개발하고 싶다면 코어 애니메이션 사용을 주저하지 말자!



소스코드

AnimButtton_for_bloging.zip





[Mask 레이어 활용] 이미지 일부만 보여주기

아이폰 2013. 1. 22. 01:31



마스크 레이어를 활용하여 이미지의 일부부만 노출시키는 예제를 작성해 보았다. 


핵심은 마스크 레이어를 사용하면 터치 이벤트를 수신할 수 없기 때문에 


상위에 터치 이벤트를 수신하여 마스크 레이어 단에 이벤트를 전달해주는 투명뷰


덮는 것 !


간단한 예제인데 fx의 설리와 크리스탈을 배경  이미지로 사용하니 재미있는 예제가 됐다. 


(나도 이제 나이 들었나봐 ㅠㅜ 바라만 봐도 행복하구나 ㅋ)


아래는 예제를 실행한 사진이다. 





자! 이제 어떻게 예제를 구현했는지 알아보자!



구현방식을 간단히 그림으로 나타내 보면 아래와 같다. 





SBTouchView 를 완전히 투명하게 해서는 안된다. 완전히 투명할 경우에는 터치 이벤트를 수신하지 못하기 때문에 


투명도를 0.1로 하는 꼼수를 발휘했다.   SBTouchView는 터치 이벤트를 단순히 nextResponder 에게 전달하고, 


SBMaskedView가 그 이벤트를 받게되고, 마스크 영역을 이동시킨다. 


구현 절차는 아래와 같다. 


1. 마스크 레이어 설정 


maskLayer = [[CAShapeLayer layer] retain];
maskLayer.backgroundColor = [UIColor whiteColor].CGColor;
        
pathRect = CGRectMake(1, 1, 118, 118);
        
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:pathRect];
maskLayer.path = path.CGPath;
maskLayer.fillColor = [UIColor blackColor].CGColor;



핵심은 레이어의 배경색을 흰색으로 처리하고, 마스킹 시킬 영역(Path)만 검정색으로 칠하는 것입니다.





2. 배경레이어에 마스크 설정 

// 배경이미지 (크리스탈, 설리 이미지)
CALayer *bgLayer = [CALayer layer];

UIImage *bgImage = [UIImage imageNamed:@"christal.jpeg"];
bgLayer.frame = [self frameForImage:bgImage];
bgLayer.contents = (id)bgImage.CGImage;
bgLayer.mask = maskLayer;

[self.layer addSublayer:bgLayer];



배경(이미지) 레이어의 mask 속성에 마스크 레이어를 설정합니다.





3. 터치뷰를 설정 

// 터치 이벤트를 잡을 투명뷰 
SBTouchView *touchView = [[[SBTouchView alloc] initWithFrame:self.bounds] autorelease];
[self addSubview:touchView];

뷰의 최상위에 Touch Event를 가로챌 SBTouchView를 덮습니다. SBTouchView는 이벤트를 수신할 수 있게 
투명도를 0.1로 설정했습니다. 



// SBTouchView
@implementation SBTouchView

- (id)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
    if (self) {
        self.backgroundColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.1];
    }
    return self;
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self nextResponder] touchesBegan:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self nextResponder] touchesCancelled:touches withEvent:event];
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self nextResponder] touchesEnded:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    [[self nextResponder] touchesMoved:touches withEvent:event];
}


SBTouchView는 보시는 것처럼 nextResponder 에게 터치 이벤트를 전달합니다. 
그러면 아래에 있는 뷰가 이벤트를 받게됩니다.



4. 터치 이벤트를 수신하여 마스크 레이어 이동 시키기 


- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint point = [touch locationInView:self];
    
    if (CGRectContainsPoint(pathRect, point)) {
        touchBegan = YES;
        touchPoint = point;
    }
}


- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    if (touchBegan) {
        UITouch *touch = [touches anyObject];
        CGPoint point = [touch locationInView:self];
        
        CGPoint destPoint = CGPointMake(pathRect.origin.x + (point.x - touchPoint.x),
                                 pathRect.origin.y + (point.y - touchPoint.y));

        pathRect.origin.x = destPoint.x;
        pathRect.origin.y = destPoint.y;
        UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:pathRect];
        maskLayer.path = path.CGPath;
        touchPoint = point;
    }
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    touchBegan = NO;
}



터치 영역이 마스크 레이어의 영역일 경우, 마스크 레이어의 위치를 이동시킵니다. 



소스 다운받기

Mask.zip




이상 마스크 레이어 사용법을 알아보면서 간단한 예제도 만들어 보았다. 


아이폰을 하면서 항상 느끼는 거지만...UI 프로그래밍이 생각보다 재밌다는 것!







  • ㅇㅈㅁ 2013.01.22 16:03 ADDR 수정/삭제 답글

    잘 배우고 갑니다 퍼갑니당!

    • 로그 @로그 2013.01.22 16:30 신고 수정/삭제

      ^^ 배우긴요~ 조금이라도 도움이 되셨으면 좋겠네요!

2011년 스마트폰 사용자 통계

앱개발로그 2012. 11. 12. 22:03




원문: http://www.go-gulf.com/blog/smartphone

Smartphone Users Statistics and Facts
Infographic by- GO-Gulf.com Web Design Company


'앱개발로그' 카테고리의 다른 글

[Android] 안드로이드앱 개발 도전기  (2) 2013.03.31
2011년 스마트폰 사용자 통계  (0) 2012.11.12
BlackBerry Architecture  (0) 2011.04.26

Objective-C 훑어보기

Objective-C 2011. 3. 20. 23:40



iPhone 의 인기와 더불어 주목받고 있는 Objective-C 언어에 대해서 간략히 알아보자. 
Objective-C 언어는 C 언어 기반위에 객체지향적인 요소를 가미한 언어로서, 객체지향 언어의 원류인  Smalltalk 에서
그 개념을 많이  차용했다고 한다.  이 포스팅에서는 언어의 구체적인 설명은 하지않고,  간략히 어떤 언어 요소들이
있는지만  알아보겠다. 



1. Objective-C 에서 클래스 정의 
#import "SuperClass.h"
@interface NewClass : SuperClass {
   int instanceVariable;
}
- method;
- methodWithParameter:param;

@end
@implementation NewClass

- method {
   return (nil);
}
- methodWithParameter:param {
   return (nil);
}



2. Object Pointers
SpecificClass * specificObject;
id anyObject;
id 타입은 void * 형 포인터로 어떤 객체 포인터도 할당하거나 할당받을 수 있다.


3. Sending Messages
[object method];
[object methodWithParam:param];

objective-c에서는 메시지를 호출한다고 하기 보단, 객체에게 메세지를 보낸다고 한다. 



3. Naming Method

Objective-C에서는 메시지의 이름을 부여할 때,  메소드가 취하는 행위를 기술하도록 이름짓는다. 메시지의 이름은
다음과  같은 형식을 따른다.
반환타입+메시지행위+첫번째 인자이름: 첫번째 인자 이름:  두번째 인자이름...
반환타입이나 인자가 존재하지 않는 경우 생략될 수 있다. 


- valuePrepositionParameter:parameter
- objectForKey:key
- stringByAppendingString:string;
- numberFromString:string;




4. Parameter and Return Types
인자와 반환값은 괄호사이에 기술한다
- (id)objectForKey:(id)aKey;
- (NSMenuItem)itemWithTag:(NSInteger)aTag;
- (unichar)characterAtIndex:(NSUInteger)index;
- (NSString*)stringByAddingPercentEscapeUsingEncoding:(NSStringEncoding)encoding;
- (void)runInNewThread;
- (void)addAttribute:(NSString*)name value:(id)value range:(NSRange)aRange;



5. Method Selectors
메서드의 이름, 인자변수의 이름, 인자의 타입이 유일한 식별자를 구성하며, Java 의 메소드 시그니처와 동일한 의미를 갖는다. 
(NSfont*)fontWithFamily:(NSString*)family traits:(NSFontTraitMask)fontTraitMask weight:(NSInteger)weight size:(CGFloat)size

의 메소드 식별자는  fontWithFamily:family:traits:weight:size 가 된다.  이 식별자는  실제로 selector 라 불리는 숫자 상수와 
맵핑이 되며, 그 숫자 상수는 SEL 타입을 갖는다.  @selector()  지시어는  특정 메소드의 selector  상수를 반환한다
SEL selector = @selector(fontWithFamily:traits:weight:size)


6. Instance Variable

isa 인스턴스 변수
모든 Objective-C 객체가 상속받는 인스턴스 변수로,  클래스 자체를 정의하는  Class  타입의 객체이다.
- (Class)class 메소드 호출을 통해 얻을 수 있으며, - (NSString *)className 통해 클래스의 이름을 문자열로 얻을 수 있다.
 

Properties
객체의 프로퍼티를 정의하는 키워드로 @property  와 @synthesize 가 있다. @property 는 클래스가 계약을 지킬 것이라고 컴파일러에게 알려주는 역할을 한다.  @synthesize 는 컴파일러에게 해당 인스턴스 변수에 대한 getter와 setter  를 생성하라고 지시한다    
 int tag;

라는 인스턴스 변수에 대해 @property int tag; @synthesize tag;를  선언되면 컴파일러는
- (void)setTag:(int)newTag  
- (int)tag

와 같은 한쌍의 접근자를 생성할 것이다. 이렇게 생성된 접근자는 디폴트로 threadsafe 하다. @property만 선언했다면, 구현파일에
 getter 와 setter를 기술하는 것은 프로그래머의 몫이다. 즉 @synthesize를 통해 자동생성하거나,   프로그래머가 직접 구현해줘야 한다. 그렇지 않으면 컴파일 에러가 발생할 것이다.

@property 지시어와 함께 사용할 수 있는 속성들이 있는데, getter와 setter울 생서을 위한 속성이 된다. 

 Property Attributes 

 Attribute  Description
 readonly  getter 만을 생성한다. readwrite 속성과 상반된다. 
 readwrite  디폴트 값이며,  getter 와 setter 를 모두 생성한다. 
 copy  setter 에서 인자를 인수변수에 저장할 때, 인자의 복사본을 저장한다.  객체 포인터 타입에만 유효한 속성이다.  (primitive 타입은 항상 복사되기 때문) 리시버가 센더의 인자값을 변경하지 못하게
할 때 사용한다. 
 assign  인스턴스 변수에 레퍼런스 카운팅 없이 단순 할당한다.  가비지 컬렉션이 존재하는 환경에서 참조를 유지하고 싶거나, 인자값이 변경불가능할 때 사용한다. 
 retain  assign과 유사하며, 가비지 컬렉션이 없는 Managed memory 환경에서 객체의 참조 카운터를 증가
시킨다. 
 getter=name 지정한 name 이름으로 getter 를 생성한다.  getter와 setter의 표준 패턴을 위반해서  Key-Value Coding 과 같은 기술들이 인식할 수 없게 될수도 있기 때문에 사용에 주의해야한다. 
 setter=name 지정한 name 이름으로 setter 를 생성한다.
 nonatomic 디폴트로 생성된  setter 는  atomic 속성을 갖는다. 락을 사용하여 thread-safe 하지만, 상당한 오버헤드를 수반한다. 쓰레드의 개입이 없을 경우,  명시적으로 nonatomic 으로 선언하여 performance 를 높이는게 좋다.
 
 

7. Class Methods
클래스 메소드는 메소드 이름 앞에 + 기호를 갖는다. 모든 클래스는 런타임에  그 클래스 인스턴스의 identity와 behavior를  정의하는 
하나의 Class  객체를 생성한다.  클래스는 이 Class 객체가 응답하는 메시지를 정의한다.  Objective-C의 클래스 메소드는 자바와 비슷한 역할을 하지만 기술적으로는 다르다.
@interface RandomSequece : NSObject {
long long int seed;
}
+ (NSNumber*)number;
+ (NSNumber*)string;
- (NSNumber*)nextNumber;
- (NSString*)string;
@end
...
NSNumber *n = [RandomSequence number];
NSString *s = [RandomSequence string];

관례적으로  클래스 메소드를 먼저 정의하고, 인스턴스 메소드를 정의한다. 자바에서 클래스메소드는 this 키워드를 사용할 수 없지만,  Objective-C 의 클래스메소드 에서는 self  키워드를 사용할 수 있다.  클래스 메소드에  self 는 Class 객체를 가리키며, 인스턴스 메소드 처럼 서브클래스에 상속할 수 있으며 오버라이딩 또한 가능하다.

@interface Classy : NSObject {
   + (void)greeting;
+ (NSString*)salutation;
}
@end

@implementation
+ (void)greeting {
NSLog(@"%@, world!", [self salutation]);
 }
+ (NSString*)salutation {
NSLog(@"Greetings");
}
@end
@interface Classic : Classy
   + (NSString*)salutation;
@end
   + (NSString*)salutation {
     return (@"Hello");
}
@end
...
[Classy greeting];    // Logs "Greetings, world"
[Classic greeting];   // Logs "Hello, world!" 

[Classic greeting]  은 Classy Class 객체에 메시지를 보내고,   [self salutation]은 해당 Cass 클래스 객체에 메시지를 보낸다.
[Classic greeting] 은 greeting 메소드가 없지만, 수퍼클래스로부터 상속받은 greeting 메시지를 호출하며, [self salutation] 은 Classic
Class 객체에 메시지를 보낸다. 

 

8. Constructing Objects
java 에서는 객체를 생성하는 문법을 존재하여, 객체를 생성하는 방법을 명시적으로 제약한다.  반면 Objective-C 는 객체를
생성하는 방법을 클래스 설계자가 결정하도록 한다. 
 
객체를 생성하는 과정은 2가지 과정을 거친다. 
1) 메모리 구조 할당
2) 인스턴스 초기화

@interface RandomSequence : NSObject {
long long seed;
}
- (id)init;
- (id)initWithSeed:(long long)startingSeed;
@end

@implementation RandomSequence
- (id)init {
self  = [super init];
if (self != nil) {  
   seed = 1;
}
return (self);
}
- (id)initWithSeed:(long long)startingSeed {
self = [super init];
if (self != nil) {
seed = startingSeed;
}
return (self);
}
@end
...
RandomSequence *r1 = [[RandomSequence alloc] init];
RandomSequence *r2 = [[RandomSequence alloc] initWithSeed:-43 ];
RandomSequence * r3 = [RandomSequence new];

alloc 은 NSObject 에 정의된 클래스 메소드 +(id)alloc   이다.  객체를 생성하기 위해 클래스 참조를 사용하여 메모리를 할당하고,
그것을  isa 변수에 할당한다. 그리고 인스턴스를 모두 0으로 채운뒤, 참조를 반환한다. 이 시점에서 요청된 객체가 존재하지만
아직 초기화는 되지않았다.  init 메시는 객체를 초기화를 담당하며, 이메시지가  반환되면 깨체는 사용할 준비가 된 상태가 된다. 
new 도 NSObject의 클래스 메소드로 내부적으로 alloc과 init 를 호출한다

- (id)init...계열의 메소드를 오버라이드 하거나 재정의 해서 객체생성을 커스터마이징 할 수 있다. 
+ (id)alloc  또는 +(id)new 를 재정의 해서는 안된다. 



9. Writing an init Method
Objective-C에서 생성자를 작성하기 위해서 4가지 계약을 충족해야 한다
1) 수퍼 클래스의 생성자를 호출한다  :  [super init]
2) 자신의  self 변수를 업데이트 한다  :  self = [super init]
3) nil 객체 검사를 한다                          : if (self != nil ) { ... }
4) 자신의 포인터를 반환한다                : return self

모든 생성자는 수퍼클래스의 생성자를 호출해야한다. 자바는 언어 차원에서 이를 지원하지만,  Objective-C 는 프로그래머에게
책임이 있다.  self 를 변경하는 것은 자바 프로그래머에게는 이상하지만,  Class Cluster를 위해 핵심이 되는 부분이다.
nil 객체 검사를 하는 이유는, 반드시 메모리 할당이 성공한다고 보장할 수 없기 때문이다. 메모리 고갈로 인한 실패가 있을 수 있기
때문에 방어적인 프로그램을 위한 것이다.  모든 잘 작성된 생성자는 위의 4가지 조건을 만족해야 한다 

Chaining Initializers
자바에서는 명시적인 생성자 호출 문법을 가지고 있어서, 특정한 수퍼클래스의 생성자나 자신의 생성자를 호출 할 수 있다.
Objective-C에서는 이러한 문법은 없지만, 동일한 원리로  수퍼클래스나 자신의 init...계열의 메시지로 초기화가 가능하다.

Designated Initializer
Objective-C 의 많은 클래스들이 "설계된" 생성자를 갖으며, 객체 생성시 이를 사용하여 초기화를 해야한다
그렇기 때문에 이러한 클래스들을 사용할 경우에는 문서를 참조해야 한다. 

Convenience Constructors
클래스 메소드의 일반적인 용례는 복잡한 객체 생성 및 초기화를 간소화시킷는 것이다. 
Cocoa framework의  NSDictrionary  클래스는 [NSDictionary dictionary]  클래스 메소드를 제공하며, 이것은
[[NSDictionary alloc] init]  와 동일하다 


 
10. Destructors
Objective-C 에서도 선택적으로 - (void)finalize 메소드를 오버라이드 할수 있다. 이 메시지는 가비지 컬렉터에 의해 객체가 파괴되기
전에 호출된다. 모든 finalize 메소드는  반환되기 직전에 수퍼클래스의 finalize  메시지를 호출해야한다
- (void)finalize {
if (file != nil) {
[file close];
file = nil;
}
[super finalize]
}   

 
   
11. etc...

Inner / Anonymous Classes (내부 클래스와 익명클래스)
내부 클래스와 익명클래스는 코드 캡슐화나 어댑터로 사용되는데, Objective-C 에서는 비공식 프로토콜과 코드 블록을
사용하여 구현된다. (코드 블록은 변수에 실행가능한 코드 블록 할당을 허용하는 Objective-C 언어에 최근 추가된 개념이다)
 
Object Arrays
Objecitve-C에서는  C object 포인터 배열이나  NSArray 컬렉션 클래스를 사용하여 구현한다
 
final
final 키워드를 지원하지 않으며, 클래스 상속이나 메소드 오버라이딩을 막을 수 없다. 변수에 적용 할 경우,  const가 유사하다.
 
abstract
Objective-C 의 모든 클래스와 메소드는 구체 클래스이다 
 
package
Objective-C에는 패키지가 없으며, 패키지 범위 또한 없다. 
 
 


'Objective-C' 카테고리의 다른 글

Objective-C 문자열 조작 메서드  (0) 2011.04.29
다중인자를 갖는 PerformSelector 메시지  (0) 2011.03.23
Protocol 과 Category  (0) 2011.03.22
Objective-C 훑어보기  (2) 2011.03.20
[Cocoa] Cocoa Design Pattern  (0) 2011.03.20
HMAC-SHA1 구현  (0) 2011.03.03
  • sungsik81 2011.03.22 09:49 ADDR 수정/삭제 답글

    날 위한 포스트인가... 두둥...

  • 로그 @로그 2011.03.23 14:00 신고 ADDR 수정/삭제 답글

    예전에 자바 개발자를 위한 objective-c 보고 정리했던 거에요.. 정리라기 보단...
    책에서 그대로 가져온...^^;;

애플리케이션 응답성 향상을 위해 동시성 사용하기

아이폰 2011. 3. 15. 23:00



1. 애플리케이션에서의 응답성

아이폰, 아이패드를 포함한 모든 애플리케이션의 사용자 인터페이스에 있어서 응답성은  중요한 요소중에 하나이다.

많은 시간을 소비하는 태스크를 사용자 인터페이스가 동작하는 쓰레드에서 수행하면, 해당 태스크가 종료하기 까지

사용자는 어떠한 인터렉션도 행할 수가 없기 때문이다.  트위터의 글목록을 가져와서 보여주는 앱을 생각해보자.

최신글 목록과 사용자 프로필 이미지를 가져오는 동안, 버벅이 되며 먹통이 되면 누가 이 앱을 사용하겠는가?

이 포스팅에서는 Cocoa  Touch에서 지원하는 동시성 향상을  방법에 대해서 알아보겠다. 

MacOsX와 iOS에서 지원하는  사용자 인터렉션 응답성을 향상시키기 위한 다음과 같은 동시성 방법들이 있다.    
NSThread / NSObject가 지원하는 백그라운드/메인 쓰레드,  C기반의 POSIX 쓰레드,  비동기 기반의 메소드,  NSOperation /NSOperationQueue 등이 있다.  각각의  추상화 수준(사용 용이성)과 복잡성이 다르다.  추상화 수준이 높은 방법일 수록 
하드웨어 의존적인 부분을 감추어주고 쉽게 사용할 수 있어서, 비지니스 로직에 더 집중 할 수 있다.  좀더 세밀한 컨트롤과
뛰어난 성능을 원한다면 C 기반의 동시성 API 를 사용하자.   



2. NSOperation /  NSOperationQueue

웹에서 사진들의 목록을 받아와 이미지와 함께 사진의 이름을 출력하는 플리커 애플리케이션을 생각해보자.

사진들의 목록을 받아오고, 목록에 해당하는 이미지 각각을 로딩하는데 상당한 시간이 소모될 것이다. 이러한 작업을

아이폰 애플리케이션의 메인쓰레드에서 수행할 경우, 애플리케이션은 상당히 버벅거리게 된다. 백그라운드 쓰레드로 

이미지를 가져오는 방법을 선택하더라도 성능의 향상은 있겠지만 그 효과는 미미하다. 

하나의 이미지를 가져오는 작업에 하나의 쓰레드를 할당하여 완료되면 이미지를 목록을 추가하는 방식이 자연스러운 

애플리케이션이 될 것이지만,  몇 개의 쓰레드를 할당하는 것이 성능의 저하를 가져오지 않고 최적의 성능을 낼 것인지는

하드웨어에 의존적이어서 정확히 알 수가 없다.  아이폰에서 제공하는 비동기 방식의 메소드를 사용하여 해결 할 수 있지만, 

좀더 추상화 수준이 높으면서 하드웨어 변경에도 코드의 변경이 거의 없는 NSOperation/NSOperationQueue 를 사용해보자.

NSOperatinoQueue 는 내부적으로 최적의 성능을 낼 수는 쓰레드의 개수를  생성하고 관리해 준다. 

NSOperation은 NSObject를 상속하고, 하나의 작업단위를 캡슐화하는 추상클래스이다. 




NSOperation을 상속하여 커스텀 클래스를 작성하거나, NSBlockOperation , NSInvocationOperation을 
사용하여 작업을 캡슐화하고 NSOperationQueue에 삽입하면 별도의 쓰레드에서 태스크를 수행할 수 있다. 
이때 그래픽 사용자 인터페이스를 변경하는 작업은 반드시 메인쓰레드상에서 수행해야함을 주의하자!!
NSOperation을 상속하는 클래스는 main 이라는 이름의 메소드에서 독립적인 작업을 캡슐화한다.
NSInvocationOperation은 별도의 쓰레드에서 호출할 객체와 셀렉터를 지정할 수 있다.  



3. NSBlockOperation 

NSBlockOperation은 Lisp, Ruby와 같은 언어의 클로져와 유사한 Block 이라는 요소를 사용한다.  Block은 마치
데이터 스콥을 공유하는 함수와 유사하며, Block으로 지정한 코드를 캡슐화하여 별도 쓰레드로 동작시킨다.
NSOperation / NSInvocationOperation 과 NSOperationQueue를 사용하는 방법도 훌륭하다.  하지만 NSBlockOperation을
사용하면 동일한 성능의 장접을 갖되,  연관된 코드를 한곳에 집중 시킬수 있는  부수적인 장점이 생긴다.
그러나 아이폰 운영체제의 하위 호환성이 떨어지는 것이 단점이다.  



 블럭은 사용하는 코드는 아래와 같다. 

NSBlockOperation의 blockOperationWithBlock 메시지의 인자로 블럭을 정의하여 인자로 전달하였다. 

블럭 ^{} 의 내부에서  긴 시간이 소모되는 코드를 수행하고, 작업이 완료되면 MainThread 에서 GUI를 변경하도록 하고 있다.

블럭 내 코드는 별도의 쓰레드에서 실행되기 때문에 자체적인 릴리지 풀을 생성하여 사용해야한다. 마지막으로 블럭에 정의한

작업을 수행하기 위해 NSOperationQueue(workQueue)에 추가하고 있다. 

 

NSBlockOperation *fetchImageOp = [NSBlockOperationblockOperationWithBlock:^{

NSAutoreleasePool *localPool;

@try {
// Autorelease pool 생성
localPool = [[NSAutoreleasePool alloc] init];
// 시간이 소요되는 작업 수행 
[selfperformSelectorOnMainThread:@selector(storeImageForURL:) // 메인쓰레드에서 GUI 변경

withObject:result 

    waitUntilDone:NO];

}

@catch (NSException * exception) {

NSLog(@"Exception: %@", [exception reason]);

}

@finally {
// Autorelease pool 해제
[localPool release];

}

}];
// NSOperationQueue에 추가 
[workQueue addOperation:fetchImageOp];  

 


4. 블럭 선언 및 정의하기
다음과 같이 익명으로 정의되어, 메시지의 파라미터로 전달될 수 있다. 

int (^cubeIt)(int);// 블럭변수 선언    
cubeIt = ^(int num) { return num * num * num; };// 블럭정의


다음과 같이 익명으로 정의되어, 메시지의 파라미터로 전달될 수 있다.

(returnDataType (^) ( parameterType1, parameterType2, ...))blockName


 



'아이폰' 카테고리의 다른 글

애플 푸시 서비스  (0) 2011.04.07
Sprite Sheet 제작툴 Zwoptex  (0) 2011.03.16
애플리케이션 응답성 향상을 위해 동시성 사용하기  (0) 2011.03.15
OpenGL ES 게임 프레임웍  (0) 2011.02.14
OpenGL ES 사용  (0) 2011.02.14
OpenGL ES 사용설정  (0) 2011.02.13

OpenGL ES 사용설정

아이폰 2011. 2. 13. 01:51


 OpenGL ES 를 사용하기 위한 설정방법을 알아보자. 
아이폰에서는 Core Animation의 CAEAGLayer 클래스를 통하여,  OpenGL ES 엔진을 사용한  그래픽 하드웨어 연산을
추상화 해준다.  UIView의 서브클래스에 일반 코어애니메이션 레이어가 아닌 CAEAGLayer 를 추가하면,   UIView의
 drawRect 메소드가 아닌 표준 OpenGL ES 함수호출을 통헤 드로잉 작업을 할 수 있게된다. 

OpenGL ES를 사용하기 위해 EAGLView 클래스를 생성해보자.  "Beginning iPhone Game Development (출판사:Apress) " 를
참고하였다. (물론 이 책도 애플 OpenGL ES 개발자 레퍼런스의 샘플을 수정한 예제를 제공한 것이다.)

1. 먼저 OpenGLES.framework와 QuartzCore.framework 를 프레젝트에 추가한다 


2. EAGLView 클래스 정의는 다음과 같다. 
#import <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES1/gl.h>
#import <OpenGLES/ES1/glext.h>

@interface EAGLView : UIView {
    
@private
    // 백버퍼의 넓이, 높이 
    GLint backingWidth;
    GLint backingHeight;
    
    EAGLContext *context;
   
 // 렌더버퍼, 프레임버퍼 식별자
    GLuint viewRenderbuffer, viewFramebuffer;
    
   // 깊이 버퍼, 사용하지 않으면 0
    GLuint depthRenderbuffer;
}

- (void)beginDraw;
- (void)finishDraw;
- (void)setupViewLandscape;
- (void)setupViewPortrait;

3.  UIView 클래스의  +(Class)layerClass 메소드를 오버라이드하여, CAEAGLayer 반환하도록 하여 OpenGL 드로잉을 가능하게 한다
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

4.  드로잉 목적에 맞게 CAEAGLayer 의 속성을 설정하고, Context 생성 
- (id)initWithFrame:(CGRect)rect {
    
    if ((self = [super initWithFrame:rect])) {
        // Get the layer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        
        eaglLayer.opaque = YES;
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
        
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES1];
        
        if (!context || ![EAGLContext setCurrentContext:context]) {
            [self release];
            return nil;
        }
		self.multipleTouchEnabled = YES;
	}
    return self;
}


5. 프레임 버퍼와 렌더링 버퍼를 생성한다. 
렌더버퍼는 색상 등의 데이터로 채워진 2차원 표면이고, 프레임버퍼는 여러 렌더버퍼로 구성된다.
(이 예제에서 프레임버터에 렌더버퍼와 깊이 버퍼를 연결하였다. )
- (void)layoutSubviews 
{
	[EAGLContext setCurrentContext:context];
	[self destroyFramebuffer];
	[self createFramebuffer];
	[self setupViewLandscape];
}

- (BOOL)createFramebuffer {    
    // 프레임버퍼와, 렌더버퍼를 위한 식별자 값 얻기 (식별자는 GLuint 형 타입)
    glGenFramebuffersOES(1, &viewFramebuffer);
    glGenRenderbuffersOES(1, &viewRenderbuffer);
    
    // 파이프라인에 바인딩 
    glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
    glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	
    // 렌더버퍼 메모리 할당
    [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(CAEAGLLayer*)self.layer];
     // 렌더버퍼를 프레임버퍼에 연결 
    glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
    
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
    glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
    
    // 깊이 버퍼도 사용한다면, 위와 동일한 과정으로 생성후 프레임버퍼에 연결
    if (USE_DEPTH_BUFFER) {
        glGenRenderbuffersOES(1, &depthRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
        glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
    }
    
    if(glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
        NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
        return NO;
    }
    
    return YES;
}

- (void)destroyFramebuffer {
    
    glDeleteFramebuffersOES(1, &viewFramebuffer);
    viewFramebuffer = 0;
    glDeleteRenderbuffersOES(1, &viewRenderbuffer);
    viewRenderbuffer = 0;
    
    if(depthRenderbuffer) {
        glDeleteRenderbuffersOES(1, &depthRenderbuffer);
        depthRenderbuffer = 0;
    }
}


6. 뷰포트와 투영변환을  설정한다 
- (void)setupViewLandscape
{		
	// Sets up matrices and transforms for OpenGL ES
	glViewport(0, 0, backingWidth, backingHeight);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glRotatef(-90.0f, 0.0f, 0.0f, 1.0f);
	// set up the viewport so that it is analagous to the screen pixels
	glOrthof(-backingHeight/2.0, backingHeight/2.0, -backingWidth/2.0, backingWidth/2.0, -1.0f, 1.0f);
	
	glMatrixMode(GL_MODELVIEW);
	
	// Clears the view with black
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
}

7.  카메라를 위치를 설정한다. 
-(void)perspectiveFovY:(GLfloat)fovY 
                        aspect:(GLfloat)aspect 
			 zNear:(GLfloat)zNear
			   zFar:(GLfloat)zFar 
{
	const GLfloat pi = 3.1415926;
	//	Half of the size of the x and y clipping planes.
	// - halfWidth = left, halfWidth = right
	// - halfHeight = bottom, halfHeight = top
	GLfloat halfWidth, halfHeight;
	//	Calculate the distance from 0 of the y clipping plane. Basically trig to calculate
	//	position of clip plane at zNear.
	halfHeight = tan( (fovY / 2) / 180 * pi ) * zNear;	
	//	Calculate the distance from 0 of the x clipping plane based on the aspect ratio.
	halfWidth = halfHeight * aspect;
	//	Finally call glFrustum with our calculated values.
	glFrustumf( -halfWidth, halfWidth, -halfHeight, halfHeight, zNear, zFar );
}


8. 렌더링한다. 
-(void)beginDraw
{
	// Make sure that you are drawing to the current context
	[EAGLContext setCurrentContext:context];
	glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
	// make sure we are in model matrix mode and clear the frame
	glMatrixMode(GL_MODELVIEW);
	glClear(GL_COLOR_BUFFER_BIT);
	// set a clean transform
	glLoadIdentity();	
}

-(void)finishDraw
{
	glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
	[context presentRenderbuffer:GL_RENDERBUFFER_OES];	
}


실제로 5번 과정까지가 EAGLView를 사용하기 위한 준비단계이며 한번 설정이 되면 거의 바뀌지 않는 부분들이다. 
6번 이후 과정부터는  애플리케이션에 따라 사용자 입력에 대한 렌더링을  수행하는 작업들이다.