iOS2012. 10. 5. 12:04

iPhone OS에서 실행되는 어플리케이션은 Core OS와 Core Services 프레임워크를 분류하여 로컬 파일시스템과 네트워크에 접근할 수 있습니다.

또한 사용자의 데이터를 저장하거나 다음 사용을 위한 어플리케이션의 상태를 저장하기 위해 파일을 읽고 쓰는 것이 가능합니다.

네트워크에 접근함으로써 네트워크상의 서버와 통신하거나 데이터를 주고 받는 등의 원격 작업을 수행할 수 있는 기능을 갖출 수 있습니다.

iPhone OS의 파일은 사용자들의 미디어파일과 개인정보와 같은 파일들과 함께 Flash 메모리를 공유하여 사용하고 있습니다.

보안상의 이유로 개발자가 만든 어플리케이션은 자신이 위치한 자신의 디렉토리에 국한하여 제한적인 데이터 읽기, 쓰기만을 할 수 있습니다.

Commonly Used Directories

보안을 위해 어플리케이션은 몇몇에 불과한 디렉토리에 접근하여 데이터와 환경 설정을 읽고 쓸수 있습니다.

어플리케이션이 장치에 설치될때 어플리케이션을 위한 홈 디렉토리가 생성됩니다. 

다음은 그 내부의 중요한 서브디렉토리의 설명입니다.

<Application_Home>/AppName.app

이 디렉토리는 어플리케이션 자신을 포함하는 Bundle 디렉토리입니다.
실행중에 이 디렉토리 내부의 파일을 변경할 수 없습니다. 
iPhone OS 2.1 혹은 그 이후의 버젼은 iTunes에서 이 디렉토리를 백업하지 않습니다.
그러나 어플리케이션을 구매했을 당시의 초기 싱크 과정에서는 컨텐츠 내용이 복사됩니다.

<Application_Home>/Documents/

이 디렉토리는 어플리케이션이 특정 데이터를 저장하기 위해 사용되는 공간입니다.
사용자의 데이터나 다른 정보를 정기적으로 저장할 수 있습니다.
이 디렉토리 내부의 컨텐츠는 iTunes에 의해서 백업 됩니다.

<Application_Home>/Library/Preferences

이 디렉토리는 어플리케이션의 특정 설정 파일을 포함합니다.
하지만 환경 설정 파일을 직접 생성하여서는 안되고 NSUserDefaults 클래스나 CFPreferences API를 사용해야 합니다.
이 디렉토리 내부의 컨텐츠는 iTunes에 의해서 백업 됩니다.

<Application_Home>/Library/Caches

이 디렉토리는 어플리케이션의 실행때마다 지속적으로 사용해야 하는 파일을 읽고 쓰기 위해 사용합니다.
개발한 어플리케이션은 일반적으로 이곳에 파일을 추가하고 삭제하여야 합니다.
iTunes는 전체 복원시에 이 디렉토리의 내용을 모두 제거 합니다.
그러므로 어플리케이션은 필요할때마다 이곳의 파일을 생성해 낼 수 있어야 합니다.
iPhone OS 2.2 혹은 그 이후의 버젼은 iTunes에서 이 디렉토리를 백업하지 않습니다.

<Application_Home>/tmp/

이 디렉토리는 어플리케이션이 실행때마다 지속될 필요가 없는 임시적인 파일을 읽고 쓰기 위해 사용합니다.
어플리케이션이 실행중이 아닐때 시스템은 파일이 더 필요한가를 판단하여 더이상 필요하지 않다고 판단이 되면 삭제합니다.
iPhone OS 2.1 혹은 그 이후의 버젼에서는 iTunes에서 이 디렉토리를 백업하지 않습니다.

Backup and Restore

어플리케이션이 백업과 복원을 위해 따로 준비해야 하는 것은 없습니다.

iPhone OS 2.2 혹은 그 이후의 버젼은 장치가 컴퓨터에 연결되어 싱크 될때 다음을 제외한 나머지 모든 파일을 증분 백업 합니다.
<Application_Home>/AppName.app
<Application_Home>/Library/Caches
<Application_Home>/tmp

어플리케이션이 매우 큰 파일을 생성하거나 매우 빈번하게 파일 엑세스를 한다면 /Documents가 아닌 /Library/Caches를 이용하는 것이 좋습니다. /Documents에 저장하게 되면 백업/복원 작업시에 시간이 더 걸리게 되는 문제점이 될 수도 있습니다.

Getting Path to Application Directories

다양한 레벨의 시스템에서 어플리케이션의 샌드박스의 위치를 알아내기 위한 다양한 프로그래밍 방식이 있어왔습니다.

하지만 Cocoa는 이러한 경로를 탐색하기 위해 다양한 프로그래밍 인터페이스를 제공합니다.

NSHomeDirectory(Foundation 프레임워크에 포함) 함수는 홈디렉토리의 경로뿐만 아니라 Documents, Library 그리고 tmp같은 디렉토리 경로를 손쉽게 얻어올수 있습니다.

또한 추가적으로 NSSearchPathForDirectoriesInDomains와 NSTemporaryDirectory함수를 사용하여 정확한 Document, Caches, tmp 디렉토리 경로를 얻어올 수 있습니다.

이 NSSearchPathForDirectoriesInDomains 함수를 사용하여 어플리케이션과 연관된 전체 경로를 알 수 있습니다.

iPhone OS에서 이 기능을 사용할려면 적절한 검색 경로를 첫번째 매개 변수로, NSUserDomainMask를 두번째 매개 변수로 지정합니다.

일반적으로 자주 사용되는 경로 상수는 다음과 같은 것들이 있습니다.

NSDocumentDirectory
<Application_Home>/Documents

NSCachesDirectory
<Application_Home>/Library/Caches

NSApplicationSupportDirectory
<Application_Home>/Library/Application Support


어플리케이션의 Documents/ 디렉토리를 찾기 위해 다음과 같이 사용합니다.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

위의 결과값을 찍어보면 /var/mobile/Applications/해쉬코드/Documents 와 같은 값이 나옵니다. 해당 경로가 어플리케이션 Documents 디렉토리의 절대 경로가 되겠죠.

두번째 인자인 NSUserDomainMask말고 다른것을 사용할 수 있습니다. 

NSSystemDomainMask와 첫번째 인자로 NSApplicationDirectory로 검색하여 보면 /Applications 라는 시스템 경로를 얻을 수 있습니다.

Reading and Writing File Data

iPhone OS는 파일을 읽고 쓰고 관리할 수 있도록 몇가지 방법을 제공하고 있습니다.

Foundation Framework :
  • 어플리케이션이 Property List를 사용한다면 NSPropertyListSerialization API를 사용하여 NSData로 변환이 가능합니다.  이렇게 변환 후에 NSData의 메서드를 이용하여 데이터를 쓸 수 있습니다.
  • 어플리케이션의 Model객체가 NSCoding 프로토콜을 채택하였다면 NSKeyedArchiver 클래스를 이용하여 객체를 저장할 수 있습니다.
  • Foundation 프레임워크는 컨텐츠 파일의 랜덤 엑세스를 위한 NSFileHandle 클래스를 제공합니다.
  • Foundation 프레임워크안의 NSFileManager는 시스템에 있는 파일을 생성하고 조작하는 메서드를 제공합니다.

Core OS calls :

  • fopen, fread, fwrite 를 호출하여 데이터를 엑덤 엑세스를 통해 읽고 쓸 수 있습니다.
  • mmap과 munmap을 호출하여 효과적으로 큰 파일을 메모리로 로드하거나 그안의 컨텐츠에 접근할 수 있습니다.

Reading and Writing Property List Data

Property List는 캡슐화 되어있는 데이터를 말합니다. 배열, 문자열, 날짜, 이진데이터, 숫자 및 Boolean 값을 포함합니다.

다른 예로 모든 Cocoa/iPhone 어플리케이션의 설정이 들어있는 Info.plist 파일을 들 수 있습니다.

코드상에서는 NSDictionary, NSArray, NSString, NSDate, NSData, NSNumber를 사용할 수 있습니다.

다음은 Property List를 NSData로 변환하여 저장하는 방법입니다.

- (BOOL)writeApplicationPlist:(id)plist toFile:(NSString *)fileName {
   
NSString *error;
   
NSData *pData = [NSPropertyListSerialization dataFromPropertyList:plist
                              format
:NSPropertyListBinaryFormat_v1_0 errorDescription:&error];
   
if (!pData) {
       
NSLog(@"%@", error);
       
return NO;
   
}
   
return ([self writeApplicationData:pData toFile:(NSString *)fileName]);
}
- (BOOL)writeApplicationData:(NSData *)data toFile:(NSString *)fileName {
   
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   
NSString *documentsDirectory = [paths objectAtIndex:0];
   
if (!documentsDirectory) {
       
NSLog(@"Documents directory not found!");
       
return NO;
   
}
   
NSString *appFile = [documentsDirectorystringByAppendingPathComponent:fileName];
   
return ([data writeToFile:appFile atomically:YES]);
}

그러면 읽어와야 겠죠. 다음의 코드를 참고하세요.
- (id)applicationPlistFromFile:(NSString *)fileName {
   
NSData *retData;
   
NSString *error;
    id retPlist
;
   
NSPropertyListFormat format;
    retData
= [self applicationDataFromFile:fileName];
   
if (!retData) {
       
NSLog(@"Data file not returned.");
       
return nil;
   
}
    retPlist
= [NSPropertyListSerialization propertyListFromData:retData
                  mutabilityOption
:NSPropertyListImmutable format:&format errorDescription:&error];
   
if (!retPlist){
       
NSLog(@"Plist not returned, error: %@", error);
   
}
   
return retPlist;
}
- (NSData *)applicationDataFromFile:(NSString *)fileName {
   
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
   
NSString *documentsDirectory = [paths objectAtIndex:0];
   
NSString *appFile = [documentsDirectory stringByAppendingPathComponent:fileName];
   
NSData *myData = [[[NSData alloc] initWithContentsOfFile:appFile] autorelease];
   
return myData;
}

id는 객체 자신을 가리키는 타입입니다. 무엇이든 가리킬 수 있는 자바의 Object라고 생각하시면 이해가 쉬울까요.

Using Archivers to Read and Write Data

실제로 프로그램이 실행할때 메모리에 수많은 객체를 생성합니다. 그리고 각각의 객체는 서로 연결되어있습니다.

이러한 객체를 파일로 저장후에 다시 읽었을때 연결된 다른 객체가 존재하지 않는다면 큰 문제가 되겠죠.

아카이브라 불리는 이 과정은 실행중에 생성된 객체들의 복잡한 상속관계를 나타내는 그래프를 모두 바이트 단위로 변환하는 과정입니다.

이것을 사용하기 위해서는 클래스가 NSCoding을 구현해야 하고 그에 따른 encodeWithCode와 initWithCoder 메서드를 구현해야 합니다.

자세한 내용은 관련 [문서]를 참고하세요. 데이터를 저장하기 위해서는 다음과 같은 방식으로 합니다.
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:[_myDataSource representation]];
[data writeToFile:myFilePath atomically:YES];

데이터를 읽어 들여 복원할때는 다음과 같이 하면 됩니다.
NSData* data = [NSData dataWithContentsOfFile:myFilePath];
id rootObject
= [NSKeyedUnarchiver unarchiveObjectWithData:data];

File Acess Guidelines

파일을 생성하고 데이터를 쓸때 다음의 가이드 라인을 지키도록 합시다.

파일을 쓰는 횟수를 최소화 해야 합니다. 파일을 엑세스 하는 작업은 느리고 제한적인 수명을 가진 플래시 메모리에 하는 행위입니다.

  • 파일의 일부분이 변경되었을때 바뀐 부분만을 쓸 수 있습니다. 적은 바이트의 변경을 위해 전체 파일을 쓰는 행위를 피하도록 합시다.
  • 파일 포맷을 정의할때, 자주쓰이는 데이터를 최대한 하나의 그룹으로 묶어 저장하면 디스크 엑세스 횟수를 최소화 할 수 있습니다.
  • 랜덤한 엑세스가 필요한 데이터 구조로 되어있는 경우 SQLite 데이터베이스에 데이터를 저장할 수 있습니다.

캐쉬 파일을 디스크에 쓰는것을 피하도록 합니다. 어플리케이션이 종료될때 다음 실행시에 동일한 상태로 시작하기 위해 저장하는 것은 예외로 합니다.

Saving State Information

사용자가 Home 버튼을 누르게 되면 iPhone OS는 당신의 어플리케이션을 종료하고 홈스크린 화면으로 돌아갑니다.

마찬가지로 URL을 여는 경우 어플리케이션이 종료하고 다른 어플리케이션의 URI가 열리게 됩니다.

이 과정은 멀티테스트 환경과 달리 현재 실행되는 어플리케이션이 종료되고 다른 어플리케이션이 수행되는 것을 뜻합니다.

이러한 작업이 자주 일어난다면 어플리케이션의 상태를 관리하는 방법을 변경할 필요가 있습니다.

사용자가 수동으로 디스크에 저장하는 작업과 달리 주요 지점에서 자동으로 저장하도록 프로그램이 변경되어야 합니다.

어플리케이션이 종료될 때 임시 캐시 파일이나 데이터베이스 환경을 이용하여 상태를 저장해야 합니다.

다음에 어플리케이션이 실행될때 저장되었던 정보를 가지고 이전 상태로 복원하는 과정을 수행하여야 합니다.

저장하는 횟수를 최소화 하여야 하지만 최대한 적절한 시점에 저장하여 이 상태를 그대로 복원하여 보여주어야 합니다.

예를 들어 연락처를 수정하고 있다가 어플리케이션을 종료하고 다시 실행했을 때 연락처의 가장 첫화면보다 수정하던 창을 그대로 보여주는 것이 좋습니다.

Networking

iPhone OS의 네트워킹 스택은 아이폰과 아이팟 터치에 있는 무선 하드웨어 장치의 여러 인터페이스를 포함합니다.

주요 프로그래밍 인터페이스로는 BSD소켓으로 만들어진 CFNetwork 프레임워크가 있습니다.

이 문서에서는 구체적인 조언만을 담고 있고 NSStream 클래스를 사용하는 방법의 정보를 원하시면 Foundation Framework 문서를 참고하시기 바랍니다.

Tip for Efficient Networking
네트워크를 통해 데이터를 주고받는 행위는 장치의 많은 전력을 소비하는 행위라는것을 기억하셔야 합니다.
데이터를 주고받는 시간을 최소화 하는 것이 배터리 수명에 도움이 됩니다.

  • 통신 프로토콜을 정의할때 데이터 포맷은 가능한한 작게 만들어야 합니다.
  • 채팅과 같은 무수히 많은 통신이 오가는 방식은 피하도록 합니다.
  • 데이터는 하나의 덩어리로 묶어 보낼 수 있습니다.

Cellular와 Wi-Fi를 이용한 통신은 아무런 행동이 없을때 파워가 꺼지도록 만들어져 있습니다. 하지만 계속해서 작은 용량의 패킷을 주고 받게 되면 이것은 지속적으로 파워가 켜져있게 되는 것을 의미하게 되며 배터리가 소모된다는 것을 뜻합니다.

그러므로 작은 데이터를 자주 주고 받는것보다는 큰 데이터를 긴 시간차를 두고 주고 받는 것이 좋습니다.

네트워크를 이용한 통신 프로그램을 작성할때는 패킷의 손실이 일어날 수 있다는 것을 기억해 두어야 합니다.

코드를 작성할때 통신 실패에 대한 핸들링을 충분히 하여야 합니다. 

그 예로 네트워크가 갑자기 사라지게 될 경우 사용자에게 알리는 시스템을 갖추는 등의 노력이 필요합니다.

'iOS' 카테고리의 다른 글

AVAudioSession 카테고리  (0) 2012.10.05
Home, Temporary, Documents, Cache Directory Path 얻기  (0) 2012.10.05
[iPhone] NSString URL 인코딩/디코딩  (0) 2012.10.05
Cycript 소개  (0) 2012.10.04
iOS - OTA ( Over the Air AdHoc )  (1) 2012.10.04
Posted by 다오나무
iOS2012. 10. 4. 14:42

항상 배포 문제로 귀찮아 했었는데..이런 방법이 있는지 이제 알았다..

이글을 쓰신분의 글 등록 날짜를 보니 2월인디...난 6개월이 지난 지금에야 알겠되어버렸으니..흠..암튼 아래 글은 람버트박이라는 분의 블로그에서 옮겨온 것이다.

출처 : http://lambert.tistory.com/407

기존에는 애드혹(AdHoc)으로 앱을 배포하기 위해서는 빌드한 바이너리를 메일 또는 다른 방법으로 클라이언트 또는 최종 테스터에게 전달하여 iTunes를 이용하여 아이폰과 동기화하는 방법을 사용했었다. 그런데 아무리 방법을 알려 줘도 앱 설치가 잘 안된다거나 혹은, 나는 못하겠다(안된다), 등의 이유로 로컬에서 직접 설치하는 방법을 주로 이용했었다. 그게 생각보다 꽤 귀찮다. 


Xcode를 이용하여 소스에서 바로 빌드하는 경우에는 그나마 용이하지만 이런저런 이유로 애드혹 바이너리를 대신 동기화 해준다는 것이 한 두 대일 경우에는 몰라도 10여대가 넘어가면 여간 성가신 일이 아닐 수 없다.

나만 이제야 알게 되었는지는 모르겠으나 OTA(Over the Air)를 이용하여 애드혹 앱을 배포하는 방법을 설명한다. 이는 안드로이드의 경우와 같이 빌드한 바이너리를 웹 서버를 이용하여 배로하는 방식이다. 물론 아이폰의 Identifier를 알고 있고 또한 iOS Provisioning Potal의 Devices에 등록하였다는 전제 하에서이다.

1. Provisioning Profile  생성
다음 그림과 같이 iOS Provisioning Potal에서 Provision Profile을 만든다. 예에서는 프로파일의 이름을 AdHocOTAProfile로 했고, 미리 AdHocOTA라는 APP ID를 만들어 두었다.


2. Provisioning Profile 설치
생성된 프로파일을 다운로드하여 설치(더블클릭하거나, Xcode의 아이콘으로 끌어다 넣거나 그외 다른 방법 이용!)한다.  설치된 프로파일은 다음 그림처럼 ~/Library/MobileDevice/Provisioning Profiles 경로에 위치해 있을 것이다. 


3. Xcode에서 Target 설정
다음은 Xcode에서 배포하려는 앱 프로젝트에서 Target을 더블 클릭하여 Code Singing 정보를 설정한다. (프로젝트 서정 정보가 아니라  Target 이다.)  위에서 생성한 AdHocOTAProfile을 선택한다.



4. Xcode에서 Buil and Archive
이제 Xcode의 메뉴에서 Build > Build and Archive를 선택하여 빌드한다.  빌드하기 전에 Simulator가 아닌 Device가 선택되었는지, 그리고 Bundle Identifier를 제대로 설정했는지 꼭 확인하자.



5. Organizer 설정
아무런 오류 없이 빌드가 완료되면 다음 그림과 같이 좌측 하단의 Archived Applications 항목이 선택된 상태에서 Organizer 화면이 나타날 것이다. 여기에서 Name 항목에 앱의 이름을 입력하고 우측 상단의 Share... 버튼을 클릭한다. 예에서는 AdHocOTA로 입력하였다.


이제 Share Archived Application 창이 나타난다. Identity 항목에서 이전에 만들어둔 Provisioning Profile을 선택하고 Distribute for Enterprise...를 클릭한다.


그러면 다음과 같이 창을 볼 수 있는데, URL과 Title 항목을 입력하고 OK 버튼을 클릭한다. 예에서는 URL: http://localhto:8080/adhoc/AdHocOTA.ipa, Titl:AdHocOTA로 하였다. 서비스를 위한 웹서버의 URL이고 AdHocOTA.ipa는 배포하려는 애드혹 앱의 이름이다. 확장자가 ipa임에 주의하라.
이후 저장할 파일이름은 당연히 AdHocOTA.ipa로 하면된다. 저장를 확인해 보면, AdHOcOTA.ipa와 AdHocOTA.plis 두 개의 파일이 생성되어 있을 것이다. 

* 나머지 항목에 대해서는 나중에 Enterprise 배포에 관한 포스트에서 다룰 예정이다.

6. HTML  생성
이제 웹 서비스를 위한 HTML을 만들차례이다. 다음은 간단한 예이나 각자 상황에 맞춰 만들면 된다. 예에서 파일명을 extension.html로 하였다. 그리고 웹서버에 해당 위치에 AdHOcOTA.ipa와 AdHocOTA.plis 그리고 방금 만든 extension.html 세 개의 파일을 올리자.

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"

<html>

<head>

<meta content='text/html;charset=UTF-8' http-equiv='Content-Type' />

<title>OTA Test App</title> 

</head> 

<body>

<ul>

<li><a href="itms-services://?action=download-manifest&url=http://192.168.0.46:8080/adhoc/AdHocOTA.plist">클릭하시면 AdHocOTA 앱을 설치하실  있습니다.</a>

</li> 

</ul> 

</body> 

</html>



7. 앱 설치
아이폰에서 사파리 웹브라우저를 실행하여 위에서 등록한 경로로 접속해보면 다음과 같을 것이다. 이제 링크를 클릭하면 앱이 설치되는 것을 확인할 수 있을 것이다.

Posted by 다오나무
iOS2012. 9. 18. 15:10

지난 강좌에서는 동기식으로 이미지를 가져오는 것을 구현했습니다. 하지만 동기식으로 데이터를 가져올 경우 용량이 큰 데이터에서 앱이 멈춰버리는 현상을 발견할 수 있습니다. 이처럼 앱이 멈추는것과 같은 현상을 제거하기 위해서는 비동기식으로 데이터를 가져와야합니다. 이번 강좌에서는 비동기식으로 데이터를 가져오는 방법, 그중에서 NSThread를 이용하는 방법에 대해서 알아보겠습니다.


 비동기식으로 데이터를 읽어온다면 앱의 사용자에게 '지금은 데이터를 로딩하는 중입니다.' 라는 것을 알리기 위한 방법이 필요합니다. 그 중에서 가장 많이 사용되는 방법은 Indicator를 사용하는 방법입니다. (데이터를 로딩하면 빙글빙글 돌아가는 그것입니다.) 프로젝트의 헤더에서 아래와 같이 구성해준 후 인터페이스 빌더에서 연결해줍니다.





 본 프로젝트에서는 뷰가 로드됨과 동시에 이미지를 읽어온다고 했지만, 개발자의 사정에 맞춰서 적당한 순간에 데이터를 로드하면 되겠습니다.



 우선 뷰가 로드되고 동시에 데이터를 읽어오므로 indicator를 start 해줍니다. 대부분의 사항은 상단의 코드와 주석을 봐도 충분히 이해 될 것이라고 생각합니다. 이 방법의 기본 개념은 '같은 스레드에서 이미지를 로딩하면 데이터가 로드되는 코드에서 앱이 멈춰버리고 코드가 진행되질 않으니 데이터를 로딩하는 부분만 다른 스레드에서 실행되게 하자' 입니다. 그래서 이처럼 데이터를 로드하는 부분만 따로 메서드로 구현해줍니다.




 데이터를 로드하는 부분에서 이처럼 데이터를 로드하고 이미지를 세팅하는 것 까지 구현해줍니다. 이제 앱을 실행시키면 Indicator가 돌아가다가 이미지의 로드가 끝나게 되면 Indicator가 사라지고 이미지가 화면에 출력됩니다. 단지 데이터를 읽어오는 부분만 리팩토링하여 스레드로 실행시키기만 하면 동기식 데이터 로드가 비동기식으로 전환 되는 것입니다.

Posted by 다오나무
iOS2012. 9. 18. 00:37

//경고창 1.

UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"A" message:@"" delegate:self cancelButtonTitle:@"확인" otherButtonTitles:@"취소"nil];

[alert show];

[alert release];


//경고창 2

UIAlertView *alert =[[UIAlertView alloc]initWithTitle:@"B" message:@"" delegate:self 

cancelButtonTitle:@"확인" otherButtonTitles:@"취소"nil];

[alert show];

[alert release];


//경고창 3

UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"C" message:@"" delegate:self 

cancelButtonTitle:@"확인" otherButtonTitles:@"취소"nil];

[alert show];

[alert release];



//경고창의 버튼 이벤트를 감지하는 델리게이트.

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex

{

//경고창의 타이틀을 비교해서 경고창을 구별한다.

if ( [[alertView titleisEqualToString:@"A"])

{

        if(buttonIndex==0){

}else {

}

    

}

else if ( [[alertView titleisEqualToString:@"B"]) 

{

if(buttonIndex==0){

}

}

else if ( [[alertView titleisEqualToString:@"C"]) 

{

if(buttonIndex==0){

}

}

}

Posted by 다오나무
iOS2012. 9. 18. 00:24

http://www.iphonedevsdk.com/forum/iphone-sdk-development/61426-multiple-action-sheets-problem.html
UIActionSheet *actionSheet1;
UIActionSheet *actionSheet2;
- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex{
if(actionSheet==actionSheet1)
{
do this}
else
if(actionSheet==actionSheet2)
{do that}
}

Posted by 다오나무
iOS2012. 9. 17. 13:14

This is the approach I use for iOS 4 and 5 compatibility:

if ([toolbar respondsToSelector:@selector(setBackgroundImage:forToolbarPosition:barMetrics:)]) {
   
[toolbar setBackgroundImage:[UIImage imageNamed:@"toolbar-background"] forToolbarPosition:UIToolbarPositionAny barMetrics:UIBarMetricsDefault];
} else {
   
[toolbar insertSubview:[[[UIImageView alloc] initWithImage:[UIImage imageNamed:@"toolbar-background"]] autorelease] atIndex:0];
}

Posted by 다오나무
iOS2012. 9. 13. 23:49

I just want to enable/disable the status bar per view controller (some view full screen, some not)

I've been several times through all the post related to the status bar 20 pixels issue, but still have the problem, especially on iOS5.0 (some trick worked on older iOS version):

Here is the problem definition:

  • I use [[UIApplication sharedApplication] setStatusBarHidden:YES] to hide the status bar

  • I always have the 20 pixel height white empty area if I do this

  • I've try to enable/disable the navigation bar to force a layout, this does not works on iOS 5:

    [self.navigationController setNavigationBarHidden:NO animated:NO];
    [self.navigationController setNavigationBarHidden:YES animated:NO];
  • I've try to manually reset the view frame size, no change

    self.view.frame=CGRectMake(0, 0, 320, 480);

  • I've tried to change manually the navigation container view:

    self.navigationController.frame=CGRectMake(0, 0, 320, 480);

  • All the view are of course 480 pixels height

share|improve this question

62% accept rate
First try to improve your accept rate it's too low – Wolvorin Jul 27 at 13:27
so you have a tip @AalokParikh ? – tomsoft Aug 8 at 17:59
I use this to show and hide the statusbar [[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone]; And also design view according to the statusbar's hidden status ie. If statusbar is hidden I design my View with no statusbar in it. :) – Wolvorin Aug 9 at 9:37
well, I've tried this too , but the beahvior also is different depending of the iOS version. I've finally had to rewrite completely the navigation framework in order to be 100% sur to manage it... – tomsoft Aug 9 at 11:59
Oh I dont get any difference or not been able to detect it sorry :( – Wolvorin Aug 9 at 12:00
show 2 more comments
Was this post useful to you?     

Use the following method in viewWillAppear of view controller to which you would like to display StatusBar.

[[UIApplication sharedApplication]setStatusBarHidden:YES];

Declare one BOOL variable to indicate whether status bar is hidden or not while view is loaded in view controller which you would like to hide status bar and set its value to NO.

BOOL statusBarHidden = NO;

Then add the following code in viewWillAppear of view controller(Status Bar is hidden in this view)

[[UIApplication sharedApplication] setStatusBarHidden:YES];
if(statusBarHidden == NO)
{
   
self.navigationController.navigationBar.frame = CGRectOffset(self.navigationController.navigationBar.frame, 0.0, -20.0);
    statusBarHidden
= YES;

}

Posted by 다오나무
iOS2012. 9. 13. 23:17

게임을 만들때 반드시 처리해야 할일 상태바를 감춰야 한다.

게임에서는 상태바에 시간 베터리 양이 보이면 상대적으로 몰입도가 떨어진다.

아무리 보드게임이라 할지라도 반드시 없애 줘야 사용자가 좀더 게임에 몰입한다.

이게 근데 어떻게 설정 해서 하는건지 모르면 난감하다.


AppDelegate 소스에

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Hide the status bar
    [UIApplicationsharedApplication].statusBarHidden = YES;

위와 같은 간단한 코드로 해결이 된다.

오늘의 팁은 끝!

Posted by 다오나무
iOS2012. 9. 13. 13:34


왼쪽의 그림은 서클(또는 휠) 메뉴이다. 적당한 이름이 생각나지 않아 그냥 서클 메뉴라 칭했다. 좌우 제스처(gesture)를 이용해 메뉴를 좌우로 이동 시킨다. 그리고 메뉴가 보이는 상테에서 상하 제스처를 이용해 서클 메뉴를 이동 시킨다. 

샘플로 첨부한 프로젝트는 이러한 애니메이션을 구현한 예이다. 실제 사용하기 위해서는 메뉴에 실제 버튼(예에서는 단순히 배경이미지만을 보여 준다.)을 추가해 이때 처리할 로직을 추가해야 한다.

원형의 메뉴를 위한 애니메이션을 위해 CGAffineTransformMakeRotation를 사용해 좌표 시스템을 이동시키는 방법을 사용했다. 그런데 이때 상하 제스처를 쉽게 사용하기 위해 CircleMenu라는 백그라운드 역할을 뷰위에 원형의 메뉴 이미지를 올려 놓았다. 이렇게 하지 않으면 좌표 시스템의 이동에 따라 상하 제스처를 제대로 이용할 수 없다.

간단히 코드를 살표 보자. 자세한 내용은 다음에 첨부한 샘플 프로젝트를 참고하라.


우선 Xcode에서 View-based Application 템플릿을 이용해 프로젝트를 생성하자. 그리고 서클 메뉴의 백그라운드로 이용할 CircleMenu 클래스를 생성한다. 실제 사용하기 위해서는 이 곳에 버튼과 관련된 내용을 추가해야 한다. 샘플에서는 제스처 이벤트만을 처리하고 있다.

그리고 CircleMenuViewController를 다음과 같이 수정한다.

[CircleMenuViewController.h]

#import <UIKit/UIKit.h>

#import <QuartzCore/QuartzCore.h>
#import <AVFoundation/AVFoundation.h> 

#import <Audiotoolbox/AudioToolbox.h>


@class CircleMenu;


@interface CircleMenuViewController : UIViewController {

    UIButton *toggleButton; // 화살표 버튼.

    float lastRadian;

    BOOL isLeft; // 화살표 이미지 상태.

}


@property (nonatomicretainCircleMenu *circle;

@property (nonatomicretainUIImageView *imageView;

@property (nonatomicretainIBOutlet UIButton *toggleButton;


- (void)rotateCircleMenu:(UISwipeGestureRecognizer *)recognizer;

- (void)moveToLeftOrRight:(UISwipeGestureRecognizer *)recognizer;

- (void)playSound;


@end


[CircleMenuViewController.m]

#import "CircleMenuViewController.h"

#import "CircleMenu.h"


#define degreesToRadian(x) (M_PI * (x) / 180.0)



@implementation CircleMenuViewController


@synthesize circle;

@synthesize imageView;

@synthesize toggleButton;


- (void)dealloc

{

    [circle release];

    [imageView release];

    [toggleButton release];

    [super dealloc];

}

// 생략...
 

- (void)viewDidLoad

{

    [super viewDidLoad];

    

    isLeft = YES;

    

    self.circle = [[CircleMenu allocinitWithFrame:CGRectMake(0.00.0225.0225.0)];

    self.circle.center = CGPointMake(420240);

    [self.view addSubview:circle];

    

    UIImage *image = [UIImage imageNamed:@"circle.png"];

    imageView = [[UIImageView allocinitWithImage:image];

    self.imageView.bounds = self.circle.frame;

    [self.circle addSubview:imageView];

    

    lastRadian = 0.0;

    

    // UISwipeGestureRecognizer 인스턴스 생성.

UISwipeGestureRecognizer *recognizerUp = [[[UISwipeGestureRecognizer allocinitWithTarget:selfaction:@selector(rotateCircleMenu:)] autorelease];

// 스와이프 제스처를 인식하기위한  .

recognizerUp.numberOfTouchesRequired = 1;

// 스와이프의 방향.

    recognizerUp.direction = UISwipeGestureRecognizerDirectionUp;

[self.circle addGestureRecognizer:recognizerUp];

UISwipeGestureRecognizer *recognizerDown = [[[UISwipeGestureRecognizer allocinitWithTarget:selfaction:@selector(rotateCircleMenu:)] autorelease];

// 스와이프 제스처를 인식하기위한  .

recognizerDown.numberOfTouchesRequired = 1;

// 스와이프의 방향.

    recognizerDown.direction = UISwipeGestureRecognizerDirectionDown;

[self.circle addGestureRecognizer:recognizerDown];

    

    UISwipeGestureRecognizer *recognizerLeft = [[[UISwipeGestureRecognizer allocinitWithTarget:selfaction:@selector(moveToLeftOrRight:)] autorelease];

// 스와이프 제스처를 인식하기위한  .

recognizerLeft.numberOfTouchesRequired = 1;

// 스와이프의 방향.

    recognizerLeft.direction = UISwipeGestureRecognizerDirectionLeft;

[self.view addGestureRecognizer:recognizerLeft];

// UISwipeGestureRecognizer 인스턴스 생성.

UISwipeGestureRecognizer *recognizerRight = [[[UISwipeGestureRecognizer allocinitWithTarget:selfaction:@selector(moveToLeftOrRight:)] autorelease];

// 스와이프 제스처를 인식하기위한  .

recognizerRight.numberOfTouchesRequired = 1;

// 스와이프의 방향.

    recognizerRight.direction = UISwipeGestureRecognizerDirectionRight;

[self.view addGestureRecognizer:recognizerRight];

    



// 생략...

#pragma mark - 커스텀 메서드


// 메뉴 회전.

- (void)rotateCircleMenu:(UISwipeGestureRecognizer *)recognizer

{   

    NSLog(@"Before frame: %@"NSStringFromCGRect(self.circle.frame));

    if (recognizer.direction == UISwipeGestureRecognizerDirectionUp) {

        lastRadian += 26;

}

if (recognizer.direction == UISwipeGestureRecognizerDirectionDown) {

        lastRadian -= 26;

}

    

    [UIView beginAnimations:@"Rotation" context:nil];

    [UIView setAnimationDuration:0.7];

    [UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];

    

    self.imageView.transform = CGAffineTransformMakeRotation(degreesToRadian(lastRadian));

    [self playSound];

    

    [UIView commitAnimations];

    

    NSLog(@"Last radian: %f"lastRadian);

    NSLog(@"After frame: %@"NSStringFromCGRect(self.circle.frame));

}


// 메뉴 이동(/).

- (void)moveToLeftOrRight:(UISwipeGestureRecognizer *)recognizer

{

    UIImage *image;

float moveCenterX;

if (isLeft && recognizer.direction == UISwipeGestureRecognizerDirectionLeft) {

moveCenterX = -100.0;

image = [UIImage imageNamed:@"icon_right.png"];

isLeft = NO;

}

if (!isLeft && recognizer.direction == UISwipeGestureRecognizerDirectionRight) {

moveCenterX = 100.0;

image = [UIImage imageNamed:@"icon_left.png"];

isLeft = YES;

}

    

    // iOS4+: Blocks 사용.

    [UIView animateWithDuration:0.7

  delay:0.1

options:UIViewAnimationCurveEaseOut

 animations:^{

 self.circle.center = CGPointMake(self.circle.center.x + moveCenterX, 240);

                         self.toggleButton.center = CGPointMake(self.toggleButton.center.x + moveCenterX,self.toggleButton.center.y);

 [self.toggleButton setImage:imageforState:UIControlStateNormal];

 } 

 completion:^(BOOL finished){

 NSLog(@"Done!");

 }];

}


// 사운드 효과.

- (void)playSound

{

    NSString *path = [[NSBundle mainBundlepathForResource:@"sound" ofType:@"mp3"];

NSURL *url = [NSURL fileURLWithPath:path];

AVAudioPlayer *player = [[AVAudioPlayer allocinitWithContentsOfURL:url error:nil];

//[player setNumberOfLoops:1];

    //[player playAtTime:1.5];

    [player play];

}


@end 


 버튼을 추가하는 방법은 여러 가지가 있겠으나, 좀더 효과적인 것이 무엇일까 고민 중이다. 좋의 의견 있는 분들은 댓글을 남겨 주십시오!

Posted by 다오나무
iOS2012. 9. 13. 13:17

UIViewController 를 이용한 메인뷰 또는 특정뷰에서 일련의 백그라운드 작업시 대기상태를 나타내주는 여러 방법(Modal Popup, Spinner, SubView 등...)들 중에, 카테고리를 이용하여 간단한 Method 호출로 UIViewController 에 오버레이되는 View 를 생성해 보자.


CREATE CATEGORY 


UIViewControll+OverlayView.h

1
2
3
4
5
6
7
#import <UIKit/UIKit.h>
 
@interface UIViewController (OverlayView)
 
-(void)showLayer:(NSString *)message;
-(void)hideLayer;
@end

UIViewController+OverlayView.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#import "UIViewController+OverlayView.h"
 
@implementation UIViewController (OverlayView)
 
-(void)showLayer:(NSString *)message
{
    UIView *layer = [[UIView alloc] initWithFrame:self.view.bounds];
    // layer.alpha = 0.5;
    layer.backgroundColor = [[UIColor clearColor] colorWithAlphaComponent:0.5];
     
    /* Create Label */
    UILabel *label = [[UILabel alloc] initWithFrame:self.view.bounds];
    label.textAlignment = UITextAlignmentCenter;
    label.lineBreakMode = YES; // 개행 설정.
    label.numberOfLines = 0; // 개행 숫자에 제한을 두지 않음.
    label.backgroundColor = [UIColor clearColor];
    label.textColor = [UIColor whiteColor];
    label.text = message;
    [layer addSubview:label];
    [label release];
     
    /* Add LayerView */
    [self.view addSubview:layer];
    [layer release];
}
 
-(void)hideLayer
{
    NSArray *subViews = [self.view subviews];
    UIView *layer = [subViews lastObject];
    [layer removeFromSuperview];
}
@end

※ alpha 를 적용하게되면, UIView *layer 에 올라오는 컨트롤 및 서브뷰들도 해당 alpha value 가 적용되어 보여진다. 반면, backgroundColor 는 해당 View 에만 alpha value 가 적용된다.


USING CATEGORY


ViewController.m

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import "ViewController.h"
#import "UIViewController+OverlayView.h"
 
@implementation ViewController
- (IBAction)onBtnAction:(id)sender {
    [self showLayer:@"MESSAGE TO SHOW"];
     
    [self performSelector:@selector(onHideLayer) withObject:nil afterDelay:2.0f];
}
 
- (void)onHideLayer
{
    [self hideLayer];
}
@end

테스트를 위해, 생성된 오버레이뷰를 performSelector 로 2초후 해제되게 작성. UIViewController 를 서브클래싱하는 클래스들에서 해당 카테고리를 임포트하여, 간단히 카테고리 메소드 호출.


RESULT


스크린샷 처럼, Spinner (UIActivityIndicatorView) 를 표시하거나 기타 다른 컨트롤뷰도 해당 오버레이뷰에 추가하여 프로젝트 성격에 맞게끔 커스터마이징.

1
2
3
4
5
6
7
8
9
/* Create Spinner */
    UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
    spinner.center = CGPointMake(160, 200);
    spinner.hidesWhenStopped = NO;
    spinner.tag = 100;
    [layer addSubview:spinner];
     
    [spinner startAnimating];
    [spinner release];

Posted by 다오나무