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]

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