2013년 12월 28일 토요일

[Xcode] Custom Code Snippet 사용하여, 코딩 생산성을 올려 봅시다.

 Xcode에서 init를 치면, 관련추가 함수가 표시되는 Code Snippet이 동작하게 됩니다.
이 코드 스니펫에 내가 원하는 Code Snippet 즉, 내가 만든 '코드 정보'를 추가할 수 있습니다.

코드 스니펫은 뭘까요?

코딩할 때, init를 치면, 추가적으로 표시되는 부분이 있습니다. Code Completion이라고 하는데, 여기에 코드 묶음이나, 함수가 자동으로 추가되는 것이 있습니다.
init를 치면, - (id) init {...}가 자동으로 추가되는 것과 같은것입니다.

여기에 내가 만든 Custom code snippet 을 추가해 봅시다.

이전 블로그에서 Keyboard관련 함수가 있었는데, 이것을 코드 스니펫에 넣어 두고, 필요한 ViewController에서 바로 사용할 수 있도록 설정해 보겠습니다.
일단 Code Snippet Library가 화면에 표시되도록 합니다.
(오른쪽 아래에 표시되는 코드 스니펫 라이브러리)
(메뉴에서 코드 스니펫 라이브러리 보기)

그리고 추가할 코드를 블록으로 선택하고, 드래그해서 코드 스니펫에 넣습니다. 아래 이미지는 이전 블로그에서 추가한 Keyboard Notification관련 코드를 스니펫으로 추가하고 있습니다.
코드를 선택해서 Code Snippet Libarary에 Drag해서 넣기

추가된 것이 표시가 되면, 클릭해서, Edit를 해서, 정보를 수정합니다.

(추가된 스니펫을 수정하기, Edit Custom Code Snippet)

  • Title: 코드스니펫 라이브러리에 표시되는 이름
  • Summary: Title 밑에 설명으로 표시됩니다.
  • Completion Shortcut : 코딩할 때, 이 숏컷을 입력에 대해서 코드스니펫이 표시가 됩니다. init를 입력하면 함수가 나오는 것 처럼.
  • Completion Scopes : 함수 내에서 사용을 하거나, 클래스 메소드에 추가될지 범위를 설정하는 것입니다.
    • All: 모든 경우에 다 표시됩니다.
    • Class Implementation : 클래스의 메소드 입력할 때 표시됩니다. 
    • Code Expression : 메소드 내에서 코드 입력할 때 표시 됩니다.


그럼 코드에서 사용해 봅시다.

위에 Compeltion Scopes를 Class implementation으로 해두어서, 함수 입력 부분에서 DBU를 치면 입력이 나와야 합니다. 아래처럼..
(함수 입력 부분에서 DBU를 치면 내가 입력해둔 코드 스니펫이 표시가 된다. Enter하면 그 코드가 추가된다.)
위와 같이 표시되고, 엔터를 치면, 코드가 추가가 됩니다.

코드에서 토큰입력할 수 있는 코드를 넣어 봅시다.

token labeled Code는 탭키로 입력부분에 바로 이동할 수 있는 토큰 코드 입니다.
(forin 입력시 표시되는 token labeled code)
입력한 코드 스니펫에 token labeled code를 추가합니다.
추가하는 방법은 <# Code #>을 입력하면 됩니다. 아래 처럼.

(코드스니펫에 token labeled code 추가하기)
 이 코드를 직접 사용하게 되면 아래와 같이 표시가 됩니다.
잘 추가해 두면 아주 유용하게 사용할 수 있을 것 같습니다.
(실제 표시된 된것)

이 코드 스니펫은 백업이 될까요? 다른 팀원과 같이 사용할 수는 없을까요?

Xcode를 다시 설치하면, 이 코드 스니펫이 다 사라지지 않을까요? 그리고, 팀원들과 같이 동시에 같은 코스 스니펫을 관리하고 사용할 수는 없을까요?
이거 아시는 분 좀 알려주세요.
저장된 XML을 Backup하고 다시 copy하면 추가가 될까요? 흠...

Custom Code Snippet이 저장되는 위치는?

/Library/Develper/Xcode/UserData/CodeSnippets/...
XML로 저장이 됩니다. 텍스트에디터로 확인을 해보면 아래처럼 나옵니다.
(XML로 저장된 Custom Code Snippet)


참고자료:



[iOS] Keyboard가 표시될 때, 사라질 때 이벤트와 그 키보드의 위치는?

 iOS에서 키보드가 표시될 때, 입력부분이 아래에 있다면, 화면이 위로 밀려 올라가야 합니다.이 때, 키보드의 크기를 알아야, 현재 화면을 위로 밀어 올릴 수 있습니다.

 UITableViewController는 자동으로 내부적으로 크기를 줄이고, 입력하는 부분을 위로 올려주게 되어 있습니다.

만약 일반적인 ViewController를 사용해서 화면을 구성한 경우에는 위치를 변경해 주어야 합니다.

1. 키보드가 화면에 나타나거나, 사라지는 Event는 무엇이고, 어떻게 알아낼까요?

키보드가 표시될 때, 전달되는 이벤트는.. 
  • UIKeyboardWillShowNotification : 키보드 표시되기 전, 전달되는 이벤트
  • UIKeyboardDidShowNotification : 키보드 표시되고 난 후, 전달되는 이벤트
  • UIKeyboardWillHideNotification : 키보드 사라지기 전,
  • UIKeyboardDidHideNotification : 키보드 사라진 후, 이벤트
  • UIKeyboardWillChangeFrameNotification : 키보드 모양이 바뀌기 전 (iOS5 이상)
  • UIKeyboardDidChangeFrameNotification : 키보드 모양이 바뀐 후 (iOS5 이상)

필요한 이벤트를 알림센터에 등록해서, 각 이벤트에 해당하는 메시지 함수를 호출 할 수 있습니다.
source code

#pragma mark - Keyboard detect function
// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
{
    NSNotificationCenter *defaultCenter = [NSNotificationCenter defaultCenter];
    [defaultCenter addObserver:self
                      selector:@selector(keyboardWillbeShown:) //표시되기 전
                          name:UIKeyboardWillShowNotification object:nil];
    [defaultCenter addObserver:self
                      selector:@selector(keyboardWasShown:)    //표시된 후
                          name:UIKeyboardDidShowNotification object:nil];
    [defaultCenter addObserver:self
                      selector:@selector(keyboardWillBeHidden:) //사라지기 전
                          name:UIKeyboardWillHideNotification object:nil];
    [defaultCenter addObserver:self
                      selector:@selector(keyboardWasHidden:)    //사라진 후
                          name:UIKeyboardDidHideNotification object:nil];
    
}

2. 호출된 키보드 이벤트 함수에서, 키보드의 크기를 알아야, 다른 컴포넌트의 크기를 조정할 수 있습니다.

 이벤트로 전달되는 NSNotification클래스의 userInfo에 해당 내용이 추가되어 있습니다.
키보드의 크기는 UIKeyboardFrameBeginUserInfoKey를 통해서 읽어 올 수 있는데, 다른 값들은 차이를 알수가 없었습니다.
  • UIKeyboardFrameBeginUserInfoKey : 키보드가 표시되기 시작할 때의 크기를 가지고 있습니다. NSValue형태로 CGRect 값을 가지고 있습니다.
  • UIKeyboardFrameEndUserInfoKey
  • UIKeyboardAnimationCurveUserInfoKey:
  • UIKeyboardCenterBeginUserInfoKey : CGPoint로 키보드 중심 위치?
  • UIKeyboardCenterEndUserInfoKey :   
  • UIKeyboardBoundsUserInfoKey
아래의 소스코드처럼, Begin과 End이 값을 각 이벤트에 따라서 읽어 봤지만 차이가 없네요.
source code
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWillBeShown: (NSNotification *) aNotification
{
    // Get Keyboard Size
    NSDictionary* info = [aNotification userInfo];
    CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
    CGSize kbSize2 = [[info objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue].size;
    double anmationDuration = [[info objectForKey:UIKeyboardAnimationCurveUserInfoKey] doubleValue];
    NSLog(@"keyboard Will beShown: %@, %@, duration:%lf", NSStringFromCGSize(kbSize), NSStringFromCGSize(kbSize2), anmationDuration);
}

3. 결과로 나오는 값은 어떻게 될까요?

iPhone 3.5-inch와 4.0-inch의 height의 길이 차이가 나지만, iPad는 Scale이 같으므로 동일한 결과가 나왔습니다.
iPhone Retina 3.5-inch
세로 
 keyboard Will beShown:   {320, 216}, {320, 216}, duration:7.
 keyboard WasShown:       {320, 216}, {320, 216}
 keyboard Will beHidden:  {320, 216}, {320, 216}
 keyboard WasHidden:      {320, 216}, {320, 216} 

가로
 keyboard Will beShown:   {162, 480}, {162, 480}, duration:7.
 keyboard WasShown:       {162, 480}, {162, 480}
 keyboard Will beHidden:  {162, 480}, {162, 480}
 keyboard WasHidden:      {162, 480}, {162, 480}

iPhone Retina 4.0-inch
세로
 keyboard Will beShown:   {320, 216}, {320, 216}, duration:7.000000
 keyboard WasShown:       {320, 216}, {320, 216}
 keyboard Will beHidden:  {320, 216}, {320, 216}
 keyboard WasHidden:      {320, 216}, {320, 216}
가로
 keyboard Will beShown:   {162, 568}, {162, 568}, duration:7.000000
 keyboard WasShown:       {162, 568}, {162, 568} //너비의 차이..
 keyboard Will beHidden:  {162, 568}, {162, 568}
 keyboard WasHidden:      {162, 568}, {162, 568}

iPad
세로
 keyboard Will beShown:   {768, 264},  {768, 264}, duration:7.000000
 keyboard WasShown:       {768, 264},  {768, 264}
 keyboard Will beHidden:  {768, 264},  {768, 264}
 keyboard WasHidden:      {768, 264},  {768, 264}
가로
 keyboard Will beShown:   {352, 1024}, {352, 1024}, duration:7.000000
 keyboard WasShown:       {352, 1024}, {352, 1024}
 keyboard Will beHidden:  {352, 1024}, {352, 1024}
 keyboard WasHidden:      {352, 1024}, {352, 1024}

iPad Retina
세로
 keyboard Will beShown:   {768, 264},  {768, 264}, duration:7.000000
 keyboard WasShown:       {768, 264},  {768, 264} //iPad와 동일.
 keyboard Will beHidden:  {768, 264},  {768, 264}
 keyboard WasHidden:      {768, 264},  {768, 264}
가로
 keyboard Will beShown:   {352, 1024}, {352, 1024}, duration:7.000000
 keyboard WasShown:       {352, 1024}, {352, 1024}
 keyboard Will beHidden:  {352, 1024}, {352, 1024}
 keyboard WasHidden:      {352, 1024}, {352, 1024}


2013년 12월 25일 수요일

[iOS] CoreData를 이용하여 데이터를 저장하자.

 iOS에서 SQLite 를 이용하여, 데이터를 저장하고 읽어 올 수 있는데, 그러기 위해서는 SQL문과 관련 지식이 필요합니다.
 하지만, CoreData를 이용하면, SQL에 대한 이해없이도, 비교적 간단하게 데이터를 저장할 수 있습니다. 
 SQL문을 직접사용하지 않더라도, 내부적으로 Wrapping이 되어서 SQLite에 저장하게 됩니다.

먼저 CoreData Framework을 사용하기 위해서는 해당 프로젝트에서 CoreData를 사용할 수 이도록 설정을 해줘야 합니다. 
 가장 간단한 방법은 프로젝트를 Empty Application으로 만들면서, CoreData사용을 체크하면, 저장을 수 있도록 기본 소스가 추가됩니다.
만약, Single Application으로 프로젝트를 이미 만들어서 사용하고 있다면, 해당 기능들만 추가하면 사용이 가능합니다. 
 추가하는 방법은 블로그의 "[iOS] CoreData를 사용하기 위해서 추가할 것들.."을 참고하여 주세요.

1. Data Model 추가하기.

 데이터베이스의 스키마와 같은 파일을 추가합니다. New File > Core Data > Data Model 을 선택해서 추가합니다.
 프로젝트에 Model.xcdatamodeld이 추가되었을 것입니다.
 이 파일에 저장하고자 하는 형태의 데이터 엔티티를 저장합니다.
 즉, 이 엔티티가 하나의 객체/단위로 저장이 될 것입니다.
 사용자가 앱을 실행한 시간을 저장을 한다고 생각하면, 현재 날짜를 가지는 Attribute를 아래와 같이 추가할 수 있습니다.




2. 추가한 데이터 모델을 저장소와 연결하기.

 AppDelegate에 추가된 managedObjectModel 함수에 1.에서 추가된 데이터 모델에 대한 파일 이름을 설정해 줍니다.
위에 데이터 모델이름이 프로젝트 생성할 때 "CoreData사용"으로 만들어지면 프로젝트 이름과 동일하게 만들어집니다.
그래서, 별도로 데이터모델 파일을 생성한 경우에는 그 파일을 이름을 위에 넣어야 변경이 됩니다.

3. 데이터 저장되는지 확인하기.

AppDelegate의 [-application didFinishLaunchingWithOptions:]함수에서 현재 시간을 저장하여 봅시다.
source code

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    
    NSManagedObjectContext *context = self.managedObjectContext;
    NSManagedObject *managedObject = [NSEntityDescription insertNewObjectForEntityForName:@"UsageStatisticDate" inManagedObjectContext:context];
    [managedObject setValue:@"2013-12-26" forKey:@"executedate"]; //날짜는 임의로 넣었습니다.
    NSError *error;
    [context save:&error];
    
    return YES;
}

4. SQLite에 저장되어 있는지 터미널로 확인해 봅시다.

위에서 저장한 것이 정확하게 저장이 되어 있는지 확인을 해보기 위해서, iPhoneSimulator에 해당 앱이 저장된 위치를 터미널로 찾아갑니다.
(Library/Application Suport/iPhone Simulator/'버전'/Applications/'.....'/Documents/)
여기에서 sqlite3로 해당 db를 열어서 내용을 확인합니다.
위에서 해당 디렉토리에서 DBUCoreDate.sqlite라는 이름의 파일이 존재합니다.
이 파일을 sqlist3 명령어로 열어서, 저장되어 있는 테이블(.tables)과 테이블 내의 데이터를 SQL문(select * from zusagestatisticdate)로 확인을 해보았습니다.
"2013-12-26"이 들어 있는 것을 볼 수 있습니다.

5. NSManagedObject를 추가해서 저장해 봅시다.

위에 4번에서는 NSManagedObject를 받아와서, setValue를 통해서 저장을 하였는데, 해당 클래스를 만들어서 저장할 수 있습니다.
Menu > Editor > Create NSManagedObject Subclass.. 를 선택하여 클래스 만듭니다.
파일이 두개가 추가됩니다.
3.에서 저장했던 방식을 아래와 같이 수정을 합니다.

source code
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    // Override point for customization after application launch.
    NSManagedObjectContext *context = self.managedObjectContext;
    
    UsageStatisticDate *usageDate = [NSEntityDescription insertNewObjectForEntityForName:@"UsageStatisticDate" inManagedObjectContext:context];
    usageDate.executedate = @"2013-12-26 using UsageStatisticDate class";
    
    NSError *error;
    [context save:&error];
    
    return YES;
}

위 파일에서는 Subclass를 이용해서 저장하게 됩니다.
저장한 것을 터미널에서 다시 확인을 해 봅니다.








[iOS] CoreData를 사용하기 위해서 추가할 것들..

내가 생성한 Single View Application에서 CoreData를 사용하기 위해서, 아래 사항들을 추가해 줍니다.
 아래에 추가되는 것은, Xcode에서 새로운 프로젝트를 만들 때, Empty Application에서 Use CoreData를 체크하였을 때 추가되는 부분입니다.

1. CoreData Framework 추가하기.

프로젝트 설정의 General에서 Linked Framework and Libraries에서 CoreData를 추가합니다.




2. Precompile Header에 CoreData.h 추가하기.

프로젝트에서 해당 클래스를 사용할 수 있도록 Prefix파일에 추가해 줍니다.
(파일이름이 '프로젝트이름-Prefix.pch'인 파일입니다.)


3. AppDelegate.h에 @Property 및 함수 추가하기

source code
@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;
@property (readonly, strong, nonatomic) NSManagedObjectModel *managedObjectModel;
@property (readonly, strong, nonatomic) NSPersistentStoreCoordinator *persistentStoreCoordinator;

- (void)saveContext;
- (NSURL *)applicationDocumentsDirectory;

4. AppDelegate.m에 관련 함수 추가하기.

source code
- (void)saveContext
{
    NSError *error = nil;
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    if (managedObjectContext != nil) {
        if ([managedObjectContext hasChanges] && ![managedObjectContext save:&error]) {
            // Replace this implementation with code to handle the error appropriately.
            // abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
}
#pragma mark - Core Data stack

// Returns the managed object context for the application.
// If the context doesn't already exist, it is created and bound to the persistent store coordinator for the application.
- (NSManagedObjectContext *)managedObjectContext
{
    if (_managedObjectContext != nil) {
        return _managedObjectContext;
    }
    
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
    if (coordinator != nil) {
        _managedObjectContext = [[NSManagedObjectContext alloc] init];
        [_managedObjectContext setPersistentStoreCoordinator:coordinator];
    }
    return _managedObjectContext;
}

// Returns the managed object model for the application.
// If the model doesn't already exist, it is created from the application's model.
- (NSManagedObjectModel *)managedObjectModel
{
    if (_managedObjectModel != nil) {
        return _managedObjectModel;
    }
    NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreData" withExtension:@"momd"];
    _managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
    return _managedObjectModel;
}

// Returns the persistent store coordinator for the application.
// If the coordinator doesn't already exist, it is created and the application's store added to it.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (_persistentStoreCoordinator != nil) {
        return _persistentStoreCoordinator;
    }
    
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"DBUCoreData.sqlite"];
    
    NSError *error = nil;
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
    if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.
         
         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
         
         Typical reasons for an error here include:
         * The persistent store is not accessible;
         * The schema for the persistent store is incompatible with current managed object model.
         Check the error message to determine what the actual problem was.
         
         
         If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
         
         If you encounter schema incompatibility errors during development, you can reduce their frequency by:
         * Simply deleting the existing store:
         [[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
         
         * Performing automatic lightweight migration by passing the following dictionary as the options parameter:
         @{NSMigratePersistentStoresAutomaticallyOption:@YES, NSInferMappingModelAutomaticallyOption:@YES}
         
         Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
         
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }
    
    return _persistentStoreCoordinator;
}

#pragma mark - Application's Documents directory

// Returns the URL to the application's Documents directory.
- (NSURL *)applicationDocumentsDirectory
{
    return [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
}


5. 사용하기.

Data Model을 추가하고, Entity를 설정한 다음 사용할 수 있습니다.
자세한 내용은  블로그([iOS] CoreData를 이용하여 데이터를 저장하자.)를 참고하여 주세요.



2013년 12월 7일 토요일

[iOS] 두 App 사이에 데이터 교환하기, ( URL 및 Pasteboard 사용)

하나의 앱(SenderApp) 에서 다른 앱(ReceiverApp)으로 정보를 전달해야 될때,
방법1) URL을 이용해서, 앱을 호출하고, URL에 데이터를 base64로 인코딩해서 보낼 수 있습니다.


방법2) 다른 방법으로 Unique Pasteboard를 만들고, 거기에 데이터를 넣고, URL을 통해서 해당 앱을 호출하고, 그 앱에서 Unique Pasteboard에서 데이터를 읽어 올 수가 있습니다.


1. URL의 데이터부분으로 전달하기.

 Receiver App의 URL Type을 등록합니다.
 dbuurl의 URL Type을 등록된 Receiver App이 설치가 되면, openURL함수를 이용해서, 호출할 수 있습니다.

Sender App에서 정보를 아래와 같이 보낼 수 있습니다.
source code

- (IBAction)sendData:(id)sender
{
    NSString *encodedData = [NSString stringWithFormat:@"iamdavidbae@gmail.com:"];
    NSString *urlQuery = [NSString stringWithFormat:@"dbuurl://localhost/data?%@", encodedData];
    
    NSURL *url = [NSURL URLWithString:urlQuery];
    if( [[UIApplication sharedApplication] canOpenURL:url] == YES )
    {   
        //데이터를 보낸다.
        [[UIApplication sharedApplication] openURL:url];
    }else{
        // openURL을 할 수 없으므로, App이 설치되지 않았거나, URL이 잘못 되었다.
    }
}

ReceiverApp에서 openURL로 호출이 되면, 아래의 AppDelegate의 handleOpenURL openURL:sourceApplication:annotation함수가 호출이 됩니다.

source code

//- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url //deprecated...
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
    NSLog(@" openURL");
    if ([@"/data" isEqual:[url path]]) {
        NSString *urlData = [url query];
        NSLog(@" handleOpenURL: query:%@", urlData);
        _viewController.text = [NSString stringWithFormat:@" handleOpenURL: query:%@", urlData];
        //그리고 base64 스트링을 디코딩해야 한다.

        //받은 데이터를 화면에 표시합니다.
        _viewController.text = urlData;
        [_viewController.view setNeedsDisplay];
        
        return YES;
    }
    return NO;
}

주의 사항!
URL로 전달하는 것이므로, 데이터가 base64로 인코딩이 되어야 합니다. 받는 부분에서는 다시 디코딩을 해야 되고..

2. Pasteboard를 사용해서 데이터를 전달하자.

위에 방법은 URL에 데이터가 공유가 되므로, 민감한 데이터는 공유하기가 힘들어지고, 모든 것을 Base64로 만들어야 하는 번거로움이 있습니다.
그래서, Unique한 Pasteboard를 만들고 거기에 데이터를 넣어서 전달 할 수 있습니다.
일단 ReceiverApp에서 URL Type을 위와 동일하게 등록을 합니다.

SenderApp에서 URL을 호출하기 전에 특정 Pasteboard에 데이터를 저장합니다.
아래에서는 "kr.pe.davidbae.pasteboard_001"이라는 이름으로 만듭니다.
source code
- (IBAction)sendData:(id)sender
{    
    NSString *urlQuery = [NSString stringWithFormat:@"dbuurl://localhost/data?%@"];
    NSURL *url = [NSURL URLWithString:urlQuery];
    if( [[UIApplication sharedApplication] canOpenURL:url] == YES )
    {
        //받는 app이 설치되어 있다. Pasteboard를 만들고, 데이터를 넣자.
        UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:@"kr.pe.davidbae.dbuurl.pasteboard_001" create:YES];
        if (pasteboard != nil) {
            pasteboard.string = sendingData; //보내는 데이터를 넣는다.
        }else{
            NSLog(@"Can't create pasteboard");
        }
        [[UIApplication sharedApplication] openURL:url];
    }else{
        // openURL을 할 수 없으므로, App이 설치되지 않았거나, URL이 잘못 되었다.
    }
}

ReceiverApp에서 handleOpenURL openURL:sourceApplication:annotation함수에서 pasteboard를 읽어서 데이터를 읽어 옵니다.
source code
//- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url //deprecated...
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    NSLog(@" openURL");
    if ([@"/data" isEqual:[url path]]) {
        NSLog(@" handleOpenURL: query:%@", urlData);
        //Pasteboard check
        UIPasteboard *pasteboard = [UIPasteboard pasteboardWithName:@"kr.pe.davidbae.dbuurl.pasteboard_001" create:NO];
        if (pasteboard != nil) {
            //정보를 읽어 온다. 읽어온 후에는 정보를 지운다.
            NSLog(@"pasteboard : %@", pasteboard.string);
            pasteboard.string = @"";
        }else{
            //Pasteboard가 없다!!
            NSLog(@"pasteboard is nil");
        }
        return YES;
    }

    
    return NO;
}


위 두가지 방법을 필요한 곳에 사용하면 되겠습니다.

3. 만들어진 Pasteboard는 언제 사라질 까요?

UIPasteboard의 설명에는, 페이스트보드를 만든 앱이 quit를 할 경우 사라진다고 되어 있는데, 직접 테스트를 해보니, 리부팅할 때까지 그대로 남아 있습니다.
Persistant속성을 넣으면, 기부팅할 때까지 계속 남아 있고, App을 uninstall하였을 경우에만 사라진다고 되어 있습니다.

2013년 12월 6일 금요일

[iOS] Pasteboard에 Copy한 데이터를 Background에서 읽어서 표시하자.

앱스토어에 등록된 Biscuit이라는 앱이, Background에 있으면서, 사용자가 Copy한 영어 단어를 받아서, 단어의 뜻을 Notification으로 알려주는 기능이 있습니다.
단어를 따로 copy해서 찾을 필요 없고, 바로 알림(Notification)으로 알려주니 정말 잘 만든 것 같습니다.
 그럼, App이 Backgound에서 어떻게 사용자가 Copy를 하였는지 알 수 있을까요?

1. 어디에 copy를 하느냐.

 사용자가 특정 String을 선택해서 copy를 하면, 시스템의 generalPasteboard에 추가가 됩니다. 이것은  UIPasteboard 를 참고하시면 됩니다.
 [UIPasteboard generalPasteboard]로 리턴되는 pasteboard는 시스템에서 공통적으로 사용하는 것으로, 리부팅해도 그대로 남아 있습니다. persist의 속성을 가지고 있습니다.
 하지만 공용으로 사용하는 것이므로, 다른 앱에서 모두다 copy를 하면 여기에 써지게 됩니다.
 그래서, 웹페이지에서 특정 단어를 Copy를 하면, 여기에 저장이 됩니다. 그래서 이 저장이 언제 변경이 되었는지 알아낼 수 있다면, Biscuit 앱처럼 기능을 구현할 수 있습니다.

2. 변경사항 알아내기 ( UIPasteboardChangedNotification )

앱이 Active되어 있는 상태에서는 UIPasteboardChangedNotification 이벤트를 NotiCenter에 등록하면, pasteboard가 변경되었을 때마다 해당 Event를 받을 수 있습니다.

source code
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changeNotification) name:UIPasteboardChangedNotification object:nil];


하지만, 앱이 백그라운드로 들어간 상태에서는 이 이벤트를 받을 수 없게 됩니다.

3. 백그라운드에서 Pasteboard의 변경사항 알아내기.

(이 방식은 Stackoverflow의 Grabbing the UIPasteboard like Pastebot while running in the background 의 답변으로 되어 있는 부분을 참고하여 만들었습니다.)
앱이 백그라운드로 들어가는 함수에서, task를 만들어서 일정시간 동안 동작하게 하고, 1초 단위로 Pasteboard에 변경이 있는지 Polling방식으로 체크를 합니다.

source code
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Create a background task identifier
    __block UIBackgroundTaskIdentifier task; 
    task = [application beginBackgroundTaskWithExpirationHandler:^{
        NSLog(@"System terminated background task early"); 
        [application endBackgroundTask:task];
    }];

    // If the system refuses to allow the task return
    if (task == UIBackgroundTaskInvalid)
    {
        NSLog(@"System refuses to allow background task");
        return;
    }

    // Do the task
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        NSString *pastboardContents = nil;

        for (int i = 0; i < 1000; i++) 
        {
            if (![pastboardContents isEqualToString:[UIPasteboard generalPasteboard].string]) 
            {
                //사용자에게 notification을 보냄.
                UILocalNotification *noti = [[UILocalNotification alloc] init];
                if(noti)
                {
                    noti.repeatInterval = 0.0f;
                    noti.alertBody = [NSString stringWithFormat:@"Pasteboard:%@ -> %@", 
                                                               pastboardContents, 
                                                               [UIPasteboard generalPasteboard].string];
                    [[UIApplication sharedApplication] presentLocalNotificationNow:noti];
                }
                //중복체크를 위해서, 데이터를 저장함.
                pastboardContents = [UIPasteboard generalPasteboard].string;
                NSLog(@"Pasteboard Contents: %@", pastboardContents);
            }

            // Wait some time before going to the beginning of the loop
            [NSThread sleepForTimeInterval:1];

            //앱이 다시 Active되면 task를 멈춰야 한다.
            if (_stopFlag) {
                break;
            }
        }

        // End the task
        [application endBackgroundTask:task];


    });


}
(원문: http://stackoverflow.com/a/10268252)

4. 동작 방식 보기




왼쪽이 Biscuit 앱을 실행하고, 'Retina'를 Copy한 상태이고, 오른쪽은 '디스플레이'를 copy해서, Pasteboard의 String이 'Retina'에서 '디스플레이'로 변경되었음을 알 수 있습니다.

5. 응용방법

가계부 등의 앱에서 문자로 들어온 카드 사용 내용을 사용자가 copy해서 붙여넣는 것이아니라, SMS에서 copy만 하면, 바로 바로 알아 내게 됩니다.


6. 그러나.. 문제점

  1) 백그라운드에서 무한정 1초마다 체크하는 것은 비효율적입니다.
  2) 애플에서 이런 방식의 Background 동작을 승인할 것인가?
  3) 10분이 지나면, 자동으로 Expired되어서 Task가 종료 됩니다.

10분이 지나면, 아래와 같이 Background에 있던 타스크가 종료됩니다. 거의 정확하게 10분이 지나면 종료가 되는 군요.


Biscuit에서는 어떻게 해결을 했는지 궁금합니다.


2013년 12월 3일 화요일

[iOS] App이 몇번 실행되었는지, 처음 설치 된지 얼마나 지났는지 알아보자

App을 개발해서 배포하고 나면, 사용자에게 평가를 요청하고, 앱스토어에 좋은 평가가 많이 모여야, 더 많은 사람들이 앱을 설치하게 됩니다.
이 평가 요청을 언제하는 것이 좋을까요? 앱을 설치하고 바로 요청하면, 사용하지도 않은 상태에서 평가를 남기게 되니까. 뭔가 강요 당한 느낌이 들어서, 좋은 평가로 이어지지 않습니다.
그렇다면, 설치 후에 5일 정도 지났고, 10번정도(하루에 2번) 실행을 한 상태에서 사용자에게 평가를 요청하면, 고객 입장에서도 충분히 써봤으니 평가를 해줄 수 있을 것입니다.

App이 실행될 때, 현재 몇번 실행이 되었는지, 처음 실행한 후에 며칠이 지났는지 알 수 있어야 합니다.

NSUserDefault에 처음 실행한 시간과, 실행한 횟수를 저장해 두면, 다음 실행할 때, 알수가 있게 됩니다.

아래와 같이 viewDidLoad 함수에서, 해당 값을 읽어서 사용하면 됩니다.

Source Code
- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view, typically from a nib.

    NSUserDefaults *userDefault = [NSUserDefaults standardUserDefaults];
    if ([userDefault valueForKey:@"DBUAppInfo"] == nil)
    {   //앱을 처음 실행한 상태로, 상태 값이 없습니다.
        //오늘 날짜와, 앱 실행 횟수를 저장합니다.
        NSDictionary *dic = [[NSDictionary alloc] initWithObjectsAndKeys:[NSDate date], @"firstRunDate", [NSNumber numberWithInt:1], @"numberOfRuns", nil];
        [userDefault setObject:dic forKey:@"DBUAppInfo"];
    }else{
        //실행한 적이 있으므로, 저장된 dictionary를 가져옵니다.
        NSDictionary *dic = [userDefault valueForKey:@"DBUAppInfo"];
        NSNumber *numberOfRuns = [dic objectForKey:@"numberOfRuns"];      //실행 횟수
        NSLog(@"running Count: %d", numberOfRuns.intValue+1);
        NSDate *firstDate = [dic objectForKey:@"firstRunDate"];          //첫 실행 날짜
        NSLog(@"firstDate: %@", firstDate);
        
        //첫 실행 후, 며칠이 지났는지 확인
        NSDate *today = [NSDate date];
        NSCalendar *calendar = [NSCalendar currentCalendar];
        NSUInteger unitFlag = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit | NSSecondCalendarUnit;
        NSDateComponents *components = [calendar components:unitFlag
                                                   fromDate:firstDate
                                                     toDate:today
                                                    options:0];
        NSLog(@"month: %ld ,day: %ld, hour:%ld, second:%ld", (long)[components month], (long)[components day], (long)[components hour], (long)[components second]);
        
        //정보를 업데이트하여, 다시 저장.
        NSDictionary *dic2 = [[NSDictionary alloc] initWithObjectsAndKeys:firstDate, @"firstRunDate", [NSNumber numberWithInt:(numberOfRuns.intValue +1)], @"numberOfRuns", nil];
        [userDefault setObject:dic2 forKey:@"DBUAppInfo"];
    }
    [userDefault synchronize]; //NSUserDefault를 동기화
    
}

위에서 "DBUAppInfo" 는 NSUserDefault에 저장하는 dictionary의 키(key)입니다.
"numberOfRuns"와 "firstRunDate"는 횟수와 처음 실행한 날짜를 dictionary에 저장하는 키(key)입니다.

2013년 11월 25일 월요일

[iOS] 초기 시작할 때, iOS의 방향성을 알기 위해서..

App이 실행되고 난 다음에는
source code
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration
{
}
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
}
함수를 통해서, 회전을 감지할 수 있지만, 시작해서, viewWillAppear 함수가 호출 될 시점에서는 Direction이 Unknown상태가 된다.
 [UIDevice currentDevice].orientation -> UIDeviceOrientationUnknown
이때는 SplitViewController의 DetailViewController의 크기를 정확하게 알 수가 없다.

StackOverflow의 http://stackoverflow.com/questions/2614274/determine-uiinterfaceorientation-on-ipad 에서

[UIApplication sharedApplication].statusBarOrientation
을 이용하면, 정확한 방향을 알수 있다고 되어 있다.
정확한 문서를 아직 찾지를 못했지만, 현재는 이것을 이용하도록 한다.

2013년 11월 18일 월요일

[iOS] 배터리 상태 감시

배터리는 UIDevice의 batteryMonitoringEnabled에 YES를 설정하고, 알림 설정을 변경 사항을 받을 수 있다.

1. 배터리 모니터링 시작하기.

Source Code
    [[UIDevice currentDevice] setBatteryMonitoringEnabled:YES];

배터리는 상태(batteryState) 의 변경사항으로 아래 4가지 상태 값을 가진다.

typedef enum {
   UIDeviceBatteryStateUnknown,
   UIDeviceBatteryStateUnplugged,
   UIDeviceBatteryStateCharging,
   UIDeviceBatteryStateFull,
} UIDeviceBatteryState;
  • 알수 없는 상태 (Unknown) : Enabled 가 되어 있지 않는 상태
  • 전원에 연결되어 있지 않는 상태 (not plugged)
  • 충전 중인 상태 (charging)
  • 완전 충전 상태 (full)
배터리의 레벨(batteryLevel) 은 0.0~ 1.0 사이의 값을 가진다.

2. 배터리 Level과 State변경 될 때, Notification받기

알림 추가를 아래와 같이 하여서, 특정 변경이 되면 특정 블록을 실행하게 한다.

source code
        [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceBatteryStateDidChangeNotification
                                                          object:Nil
                                                           queue:[NSOperationQueue mainQueue]
                                                      usingBlock:^(NSNotification *note) {
                                                          NSLog(@"Battery State Change");
                                                          //Show Battery State
                                                      }];
        [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceBatteryLevelDidChangeNotification
                                                          object:nil
                                                           queue:[NSOperationQueue mainQueue]
                                                      usingBlock:^(NSNotification *note) {
                                                          NSLog(@"Battery Level Change");
                                                          //Show Battery Level
                                                      }];

3. 정보 읽어 오기

배터리의 state와 level은 아래와 같이 읽어 올 수 있다.

source code
    [UIDevice currentDevice].batteryState;
    [UIDevice currentDevice].batteryLevel; // 0.0f - 1.0f


4. 그럼, 이것을 어디에서 사용할 수 있는가?

특정 연산을 시작하거나, 데이터를 업로드할 때, 전원이 연결되어 있는지, level이 특정 이상인지를 체크해서 사용자에게 연결하거나, 업로드를 못하도록 할 수 있다.

사용자들이 전원이 없는 상태에서 무리하게 실행하는 것을 막을 수 있는 것이다.


[iOS] ScreenShot으로 현재 화면을 UIImage로 만들자


사용자에게 현재 화면의 ScreenShot 을 만들어서 Email에 첨부하고 싶다면..
CALayer의 -renderInContext를 이용해서 화면을 그대로 이미지로 만들 수 있다.

(출처: https://developer.apple.com/library/ios/qa/qa1703/_index.html)

먼저 스크린의 크기를 가져와야 되는데, iOS4부터 Retina Display가 나오면서, Scale로 구분을 하도록 변경이 되었다. 그래서, scale을 고려해서 화면 Size를 가져와야 한다.

화면크기를 확보를 했으면, 이제 UIContextRef를 가져오고, 이미지를 읽어 온다.

Source Code

- (UIImage*)screenshot 
{
    // Create a graphics context with the target size
    // On iOS 4 and later, use UIGraphicsBeginImageContextWithOptions to take the scale into consideration
    // On iOS prior to 4, fall back to use UIGraphicsBeginImageContext
    CGSize imageSize = [[UIScreen mainScreen] bounds].size;
    if (NULL != UIGraphicsBeginImageContextWithOptions)
        UIGraphicsBeginImageContextWithOptions(imageSize, NO, 0);
    else
        UIGraphicsBeginImageContext(imageSize);

    CGContextRef context = UIGraphicsGetCurrentContext();

    // Iterate over every window from back to front
    for (UIWindow *window in [[UIApplication sharedApplication] windows]) 
    {
        if (![window respondsToSelector:@selector(screen)] || [window screen] == [UIScreen mainScreen])
        {
            // -renderInContext: renders in the coordinate space of the layer,
            // so we must first apply the layer's geometry to the graphics context
            CGContextSaveGState(context);
            // Center the context around the window's anchor point
            CGContextTranslateCTM(context, [window center].x, [window center].y);
            // Apply the window's transform about the anchor point
            CGContextConcatCTM(context, [window transform]);
            // Offset by the portion of the bounds left of and above the anchor point
            CGContextTranslateCTM(context,
                                  -[window bounds].size.width * [[window layer] anchorPoint].x,
                                  -[window bounds].size.height * [[window layer] anchorPoint].y);

            // Render the layer hierarchy to the current context
            [[window layer] renderInContext:context];

            // Restore the context
            CGContextRestoreGState(context);
        }
    }

    // Retrieve the screenshot image
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}
(출처: https://developer.apple.com/library/ios/qa/qa1703/_index.html)


위 함수를 이용해서 이미지를 읽어오면, 디바이스의 화면 크기에 맞는 이미지가 생성이 된다.
이것을 그대로 Email로 첨부를 하게 되면, 5.4MB정도 크기가 되어서 보내기 부담스러워 진다.
그렇게 되면 이미지의 크기를 변경해서 이미지를 표시를 하고, 그 이미지를 바탕으로 data를 가져오면 될 것이다.
큰이미지를 작은 이미지로 리사이징 하는 것은 이전 블로그(http://hidavidbae.blogspot.kr/2012/12/ios6.html)를 참고하여 주시길..

2013년 11월 13일 수요일

[iOS] 설치된 App에서 Vendor를 위한 고유 식별 ID identifierForVendor

1. identifierForVendor란?

 UIDevice 클래스의 속성인 identifierForVendor를 이용하여 설치된 앱에서 Vendor를 위한 공유 ID를 얻을 수 있습니다.
이 속성의 값은 NSUUID 클래스이고 UUIDString을 값으로 참조할 수 있습니다.
또한 하나의 iOS 장치에서 실행되는 같은 공급 업체에서 온 애플리케이션에 대해 동일합니다.
즉, 내 iPhone에서 같은 업체에서 만든 앱들에서 읽으면 동일한 값이 나온다는 것입니다.
A사가 만든 App1, App2에서 identifierForVendor를 읽으면 동일한 값을 얻을 수 있습니다.

같은 기기에서라도, Vendor가 다르면 App에서 읽는 값이 달라집니다.
다른 기기이면 물론 다른 값을 나타내고..

Vendor에 대한 것은 일반적으로 공급 업체가 앱 스토어에서 제공 한 데이터에 의해 결정됩니다.
그리고, 한 기기에서 A사가 만든 모든 App이 삭제가 되고, 다시 설치가 되면, 이 ID는 달라질 수 있습니다.

그리고, 앱을 앱스토어에 제출하지 안은 경우에는 번들 ID로 넣은 값의 처음 두 구성요소를 바탕으로 만듭니다. com.example.app1, com.example.app2 이면 동일한 공급업체 ID를 나타내는 것이 됩니다.

사용 소스는 아래와 같습니다.

Source Code
    UIDevice *device = [UIDevice currentDevice];
    NSUUID *uuid = device.identifierForVendor;
    NSLog(@"identifierForVendor     : %@", uuid.UUIDString);

2. 활용 방법은?

그러면, 이것을 가지고 뭘 이용할 수 있을까요?
설치할 때, 사용자 정보와 idForVendor를 같이 저장을 해 두면, 다른 기기에서 접속하였는지를 알 수가 있습니다. (단, 삭제했다가 다시 설치하면, 다른 기기로 인식할 수도 있습니다.)
유일하게 하나의 기기에서만 동작하게 앱을 만들어야 된다면, 로그인할 때, 해당 idForVendor를 같이 보내고, 같은 idForVendor에서 접속한 것이면, 바로 로그인 할 수 있도록 만들 수가 있겠습니다.
만약 다른 idForVendor를 가지고 접속하면, 이전 idForVendor를 삭제하여서, 유일하게 하나의 기기에서만 접속하도록 할 수 있겠습니다.

Facebook 앱에서 기기접속에 대한, 인증서 요구도 이것을 이용하였을 것 같습니다.
카카오톡에서 기기 인식도 이 값을 이용하지 않을까요?

참고사이트
앱 스토어의 UDID 사용제안에 따른 대안들

2013년 11월 9일 토요일

[iOS] Platform String 정리

iOS기기별로 model로는 개별적으로 구분할 수 없지만, platform 이름으로 구분할 수 있습니다
아래에는 기기의 플랫폼을 읽어오는 부분과, 해당 값이 어떤 기기를 나타내는지 알려주는 코드입니다.
(출처: http://theiphonewiki.com/wiki/Models)
(소스 출처: http://stackoverflow.com/questions/12505414/whats-the-device-code-platform-string-for-iphone-5-5c-5s-ipod-touch-5)

- (NSString *) platform
{
    int mib[2];
    size_t len;
    char *machine;

    mib[0] = CTL_HW;
    mib[1] = HW_MACHINE;
    sysctl(mib, 2, NULL, &len, NULL, 0);
    machine = malloc(len);
    sysctl(mib, 2, machine, &len, NULL, 0);

    NSString *platform = [NSString stringWithCString:machine encoding:NSASCIIStringEncoding];
    free(machine);
    return platform; 
}

- (NSString *) platformString
{
    NSString *platform = [self platform];
    if ([platform isEqualToString:@"iPhone1,1"])    return @"iPhone 1G";
    if ([platform isEqualToString:@"iPhone1,2"])    return @"iPhone 3G";
    if ([platform isEqualToString:@"iPhone2,1"])    return @"iPhone 3GS";
    if ([platform isEqualToString:@"iPhone3,1"])    return @"iPhone 4";
    if ([platform isEqualToString:@"iPhone3,3"])    return @"Verizon iPhone 4";
    if ([platform isEqualToString:@"iPhone4,1"])    return @"iPhone 4S";
    if ([platform isEqualToString:@"iPhone5,1"])    return @"iPhone 5 (GSM)";
    if ([platform isEqualToString:@"iPhone5,2"])    return @"iPhone 5 (GSM+CDMA)";
    if ([platform isEqualToString:@"iPhone5,3"])    return @"iPhone 5C (GSM)";
    if ([platform isEqualToString:@"iPhone5,4"])    return @"iPhone 5C (GSM+CDMA)";
    if ([platform isEqualToString:@"iPhone6,1"])    return @"iPhone 5S (GSM)";
    if ([platform isEqualToString:@"iPhone6,2"])    return @"iPhone 5S (GSM+CDMA)";

    if ([platform isEqualToString:@"iPod1,1"])      return @"iPod Touch 1G";
    if ([platform isEqualToString:@"iPod2,1"])      return @"iPod Touch 2G";
    if ([platform isEqualToString:@"iPod3,1"])      return @"iPod Touch 3G";
    if ([platform isEqualToString:@"iPod4,1"])      return @"iPod Touch 4G";
    if ([platform isEqualToString:@"iPod5,1"])      return @"iPod Touch 5G";

    if ([platform isEqualToString:@"iPad1,1"])      return @"iPad";
    if ([platform isEqualToString:@"iPad2,1"])      return @"iPad 2 (WiFi)";
    if ([platform isEqualToString:@"iPad2,2"])      return @"iPad 2 (GSM)";
    if ([platform isEqualToString:@"iPad2,3"])      return @"iPad 2 (CDMA)";
    if ([platform isEqualToString:@"iPad2,4"])      return @"iPad 2";
    if ([platform isEqualToString:@"iPad3,1"])      return @"iPad-3G (WiFi)";
    if ([platform isEqualToString:@"iPad3,2"])      return @"iPad-3G (4G)";
    if ([platform isEqualToString:@"iPad3,3"])      return @"iPad-3G (4G)";
    if ([platform isEqualToString:@"iPad3,4"])      return @"iPad-4G (WiFi)";
    if ([platform isEqualToString:@"iPad3,5"])      return @"iPad-4G (GSM)";
    if ([platform isEqualToString:@"iPad3,6"])      return @"iPad-4G (GSM+CDMA)";

    if ([platform isEqualToString:@"iPad2,5"])      return @"iPad mini-1G (WiFi)";
    if ([platform isEqualToString:@"iPad2,6"])      return @"iPad mini-1G (GSM)";
    if ([platform isEqualToString:@"iPad2,7"])      return @"iPad mini-1G (GSM+CDMA)";
    if ([platform isEqualToString:@"iPad4,4"])      return @"iPad mini-2G (WiFi)";
    if ([platform isEqualToString:@"iPad4,5"])      return @"iPad mini-2G (GSM)";



    if ([platform isEqualToString:@"i386"])         return @"Simulator";
    if ([platform isEqualToString:@"x86_64"])       return @"Simulator";
    return platform; 
}

참고가 되시길 바랍니다.

2013년 11월 7일 목요일

[iOS] 근접센서 값을 읽어 오자.

iPhone에서 화면에 얼굴을 가까이 대면, 근접센서로 인식해서 화면이 터치가 안되게 끄는 기능이 있다.
이 기능을 App에서 이용을 하기 위해서, 아래와 같이 설정하는 것이 필요하다.

- (void)viewDidLoad
{
    [super viewDidLoad];
 // Do any additional setup after loading the view.
    
    [[NSNotificationCenter defaultCenter] addObserverForName:UIDeviceProximityStateDidChangeNotification
                                                      object:nil
                                                       queue:[NSOperationQueue mainQueue]
                                                  usingBlock:^(NSNotification *note) {
                                                      NSLog(@"The Proximity sensor(%@)", [UIDevice currentDevice].proximityState?@"On":@"Off");
                                                      NSLog(@"name:%@, userInfo:%@", note.name, note.userInfo);
                                                  }];
}

센서를 ON 하기 위해서는 아래와 같이 값을 설정한다.
    BOOL isEnabled = [UIDevice currentDevice].proximityMonitoringEnabled;
    [UIDevice currentDevice].proximityMonitoringEnabled = !isEnabled;

근접센서는 iPhone에서는 동작이 되지만, iPad에서는 설정을 해도 계속 NO가 된다.
즉, iPad에는 근접센서가 없다.

[iOS] Photo Library접근시 사용자에게 표시 문구 넣기.

먼저 UIImagePickerController 이전 블로그를 참고하여 주세요.

iOS기기에서 Photo Library에 접근하려고 하면, Privacy로 사용자에게 접근을 할 것인지 물어보게 됩니다. iOS에서 표시하는데, 거기에 사용자에게 필요한 문구를 표시해 줄 수 있습니다.
 key값은 Privacy - Photo Library Usage Description이고, Value로 string을 입력하면 됩니다.

위와 같이 info.plist파일에 추가를 하면, 사용자에게 최초 접근 할 경우에, 아래와 같이 표시가 됩니다.

info.plist에 값을 넣은 경우 입니다.
info.plist에 값이 없는 경우
만약, 다국어 지원을 위해서는 info.plist파일을 다국어 적용하면 되겠습니다.

2013년 11월 3일 일요일

[iOS] NSUserDefaults 를 사용한 설정값 간단 저장 방법

App 내에서 설정값을 저장하고 싶은데, 파일로 저장하기에는 번거로운 것이 많이 필요하므로, NSUserDefaults를 사용해서, 간단히 저장하는 방법을 정리합니다.

NSUserDefaults 클래스는, 디폴트시스템에 접근할 수 있는 방법을 제공하고 있습니다.
사용자의 기본데이터베이스에 값을 저장하도록 함으로써 간단하게 preference를 설정할 수 있게 하는 것입니다.

사용방법은 아래와 같이 standardUserDefaults를 가져와서 데이터를 셋팅하고 동기화하여 줍니다.

- (void) saveDebugInfoData:(BOOL) value
{
    [[NSUserDefaults standardUserDefaults] setObject:(value?@"YES":@"NO") forKey:SETTINGS_SHOW_DEBUG_INFO];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

그리고, 값을 읽기 위해서는 아래와 같이 읽어 오면 됩니다.

- (void)viewWillAppear:(BOOL)animated
{
    self.debugTextView.hidden = ![[NSUserDefaults standardUserDefaults] boolForKey:SETTINGS_SHOW_DEBUG_INFO];
}

Developer Library에 보시면, 각 데이터 타입에 따라서 Setter/Getter가 준비되어 있습니다.




[iOS6] App내에서 이메일을 보내자.

개발하는 App 안에서 개발자에게 문제점이나 요청사항을 보낼 수 있는 기능이 있어야 합니다.
즉, 개발자에게 이런 저런 기능이 있으면 좋겠다거나, 이럴때 문제가 발생해서 불편하다거나 하는 내용을 개발자에게 보낼 수 있는 기능은 있어야 합니다.
 간단하게 MFMailComposeViewController를 사용해서, 기본으로 제공하는 이메일을 사용할 수 있습니다.

위와 같이 Framework은 MessageUI를 추가해 주시고, 사용하는 곳에서
#import <MessageUI/MessageUI.h>를 해주시면 사용할 수 있습니다.
메일 보내기 위해서, MFMailComposeViewController를 만들고, 메일에 들어갈 내용을 각각 넣어 줍니다.

NSString *iOSVersion = [[UIDevice currentDevice] systemVersion];
    NSString *model = [[UIDevice currentDevice] model];
    NSString *version = FGS_VERSION;
    NSString *build = FGS_BUILD;
    MFMailComposeViewController *mailComposer = [[MFMailComposeViewController alloc] init];
    mailComposer.mailComposeDelegate = self;
    [mailComposer setToRecipients:[NSArray arrayWithObjects: @"ask.davidbae@gmail.com",nil]];
    [mailComposer setSubject:[NSString stringWithFormat: @"%@ V%@ (build %@) Support", NSLocalizedString(@"Family GoStop", @"App Name"),version,build]];
    NSString *supportText = [NSString stringWithFormat:@"[Device: %@]\n[iOS Version:%@]\n",model,iOSVersion];
    supportText = [supportText stringByAppendingString: NSLocalizedString(@"Please describe your problem or question.", @"")];
    [mailComposer setMessageBody:supportText isHTML:NO];

    [self presentViewController:mailComposer animated:YES completion:nil];

그리고, 호출하는 클래스에서 MFMailComposeViewControllerDelegate 프로토콜을 추가해 주시고, 아래 함수를 구현하면, 메일이 정상적으로 보내졌는지 확인이 가능합니다.

#pragma mark - MFMailComposeViewControllerDelegate

- (void)mailComposeController:(MFMailComposeViewController *)controller
          didFinishWithResult:(MFMailComposeResult)result
                        error:(NSError *)error
{
    [self dismissViewControllerAnimated:YES completion:nil];
    /*
     enum MFMailComposeResult {
     MFMailComposeResultCancelled,
     MFMailComposeResultSaved,
     MFMailComposeResultSent,
     MFMailComposeResultFailed
     };
     */
    NSLog(@"Email Send Result: %d", result);
    if (result == MFMailComposeResultFailed) {
        NSLog(@"Support mail failed: Error Code:%d, %@", error.code, [error description]);
    }
}
위에서 result값으로 보내 졌는지, 취소 되었는지, 실패 되었는지 확인이 가능합니다.
그리고, 메일을 보내기 위해서, Compose를 보여주기 전에, 실제 메일을 보낼 수 있는지 확인이 되어야 합니다.
그러기 위해서는, 클래스 메소드를 사용하여 확인이 가능합니다.

    [MFMailComposeViewController canSendMail]

위 확인코드는 메일을 보내기 위한 버튼을 보여주기 전에 확인하여야 하고, 메일을 보낼 수 없는 경우, 사용자에게 메일 작성 화면을 보여주면 안됩니다.
 왜? 어렵게 메일을 작성했는데, 메일 설정이 안되어 있어서 보낼 수 없으면, 허탈하겠죠.
이상입니다.

2013년 10월 14일 월요일

[iOS] 자동 Sleep 모드로 안 가도록 만드는 방법

예전 버전에서 부터 간단하게 설정하던 것인데, 여기 적어 둡니다.

[UIApplication sharedApplication].idleTimerDisabled = NO;

위와 같이 설정을 하면, 자동으로 idle로 넘어가지 않습니다.


2013년 10월 12일 토요일

[iOS7] 국가별 프로그램명 적용하기.

이건 iOS7에만 해당 되는 것이 아니고, 이전 지역화와 다 관련이 있지만, 일단 iOS7으로 테스트하니, iOS7으로 올립니다.

프로그램의 이름은 InfoPlist.strings의 Bundle display name과 관련이 있습니다.
이 내용은 InfoPlist.strings에 추가하면 됩니다.

위에 파일의 내용을 아래와 같이 추가합니다.


그리고, 실행을 하면, 한글의 경우, 프로그램이름이 지역화 테스트로 표시가 됩니다.





2013년 10월 11일 금요일

[iOS7] 다국어 스트링 지원하기.

iOS App에서 다국어를 지원하는 부분은 크게 3가지로 구성이 된다.
  • 다국어 스트링 적용하기
  • 다국어 리소스 적용하기
  • 국가별 프로그램 명 적용하기
이번 포스트에서는 다국어 스트링 적용만 알아보자.

다국어 스트링 적용하기 위해서, 신규파일로 Localizable.strings 파일을 생성한다.


위와 같이 New > File > iOS Resource > Strings File 선택하고, 이름을 Localizable로 입력한다.

파일이 추가 되었으면, 추가된 Localizable.strings를 선택하고, File Inspector를 통해서 Localize.. 시킨다. 현재 파일을 base로 만든다.
(Xcode 5 기준으로 위와 같이 생성되고, Xcode 4에서는 언어를 선택하게 되어 있다.)

Localize..를 하면, 아래와 같이 Based와 English를 생성이 된다. 

추가 언어를 넣기 위해서는 Project에서 Localization에 언어를 추가하면 된다.

그러면, 소스 코드에서 아래와 같이 사용하면 된다.

    NSLog(@"Name: %@", NSLocalizedString(@"Name", @"Name"));
    NSLog(@"Name: %@", NSLocalizedString(@"Text", @"Text"));
    NSLog(@"MyName: %@", NSLocalizedString(@"MyName", @"Name"));
    NSLog(@"MyText: %@", NSLocalizedString(@"MyText", @"Text"));

위와 같이 사용하면, Default로 Localizable.strings 파일에서 키("Name", "Text", "MyName", "MyText")에 대한 String을 찾아서 표시하게 된다.
만약, Localizable.strings외에 추가로 MyLocalizable.strings를 추가했을 경우, 거기에서 읽어 올 수 있는 방법이 있는가?
 기본적으로  NSLocalizedString은 NSBundle의

- (NSString *) localizedStringForKey: value: table:; 함수를 호출해서 읽어 오는데, table에 전달되는 것이 nil 이거나 파일이 없을 경우, Localizable.strings 파일에서 찾게 되는 것이다.
아래와 같이 직접 NSBundle을 이용해서 MyLocalizable을 넣어 주면, MyLocalizable.strings에서 찾아서 표시하게 된다.











위 코드의 결과를 아래와 같이 볼 수 있다.











여기서, Localizable.strings(Base) 에는
"Name" = "Name";
"Text" = "Text";
만 있고, Localizable.strings(English) 에는
"Name" = "이름 (en)";
"Text" = "텍스트 (en)";
이 있어서, "MyName", "MyText"에 대해서는 Default값이 표시가 된다.

여기서, MyLocalizable.strings(Base) 에는
"MyName" = "MyName";
"MyText" = "MyText";
만 있고, MyLocalizable.strings(English) 에는
"NyName" = "My 이름 (en)";
"MyText" = "My 텍스트 (en)";
이 있어서 "Name"과 "Text"에 대해서, Value로 전달되는 값이 표시된다.








2013년 9월 15일 일요일

[iOS6] App개발에서 사용할 수 있는 Font 목록, 추가 한글 폰트 등록하여 적용하기.

폰트 적용하기.

iOS6에서 한글 폰트는 AppleSDGothicNeo-Medium 과,  AppleSDGothicNeo-Bold가 기본으로 등록되어 있습니다.

이 폰트를 사용하기 위해서는 UILabel이나 UIButton의 폰트로 등록해 주면 됩니다.


//h 파일에서..
@property (weak, nonatomic) IBOutlet UILabel *koLabel;
//m 파일에서..
self.koLabel.font = [UIFont fontWithName:@"AppleSDGothicNeo-Bold" size:17.0f];
//기본으로 AppleSDGothicNeo이므로 별 차이가 없다.


기본적으로 적용되어 있는 System 폰트와 비교를 하면 별 차이가 없습니다.
Label과 button에 적용해서 비교를 해보자. 아래 이미지를 보면, 거의 차기 없는 것 같습니다.

시스템 폰트는 System-Regular(Helvertica), 한글기본 폰트는 AppleSDGothicNeo-Medium입니다

한글 폰트 등록해서 사용하기.

네이버의 NanumGothic과 NanumBrush를 등록해서 표시해 보자. 폰트는 Naver에서 검색하면 나옵니다. (http://hangeul.naver.com/font)
폰트(ttf)파일을 받아서, 프로젝트에 등록하고, ~-info.plist 에 폰트 이름을 등록하고 사용할 수 있습니다.

//h 파일에서..
@property (weak, nonatomic) IBOutlet UILabel *koLabel;
//m 파일에서..
self.koLabel.font = [UIFont fontWithName:@"NanumBrush" size:17.0f];
// Naver의 나눔손글씨 붓 폰트가 적용이 되었다.
적용한 것을 한번 보면.
위에 손글씨 붓 폰트가 적용된 것을 볼 수 있다. 크기를 같게 하면, 손글씨 붓 폰트는 좀 작습니다.

사용가능한 폰트 리스트 가져오기.

현재 디바이스에서 지원 가능한 폰트의 리스트를 읽어서 어떤 것이 지원되는지 확인해 봅니다.
iOS6 Simulator에서는 61개의 font family 이름이 있고, 이것에 각각 Bold나 Medium이 있을 수 있으니, 지원하는 폰트는 61개 정도로 볼 수 있겠습니다.
 //UIFont에서 지원하는 폰트를 알아보자.
 NSArray *fonts = [UIFont familyNames];
 NSLog(@"Available fonts : %@", fonts);

 //Family Name에서 지원하는 폰트 리스트는 아래 함수로 읽어 올 수 있다.
 NSArray *fontList = [UIFont fontNamesForFamilyName:_familyFontName];

  그러면 이제 지원하는 폰트들을 화면에 표시를 해볼까요?


위에 자세히 보면, NanumBrush와 NanumGothic도 사용 가능한 폰트로 등록이 되어서 리스트에 나와 있는 것을 볼 수 있습니다.