2013년 1월 15일 화요일

[iOS] ZBarSDK를 이용한 바코드 리더 개발

ZBarSDK의 적용 및 커스터마이징은 문서화가 잘되어있다. 여기서는 기본적인 사용법만 간단히 기록한다. XCode 4.5 / iOS 6.0 SDK 개발환경에서 iPhone 4S를 이용해 테스트했다.

준비

ZBarSDK 1.2 다운로드

프로젝트 생성 및 ZBarSDK 추가

 Single View Application 템플릿을 이용하여 새 프로젝트를 하나 만든다. 여기서는 ZBarSample을 사용한다. 프로젝트가 생성되면 Project Navigator에서 프로젝트를 선택하고 "Add File to..."를 선택하여 ZBarSDK를 프로젝트에 추가한다. Destination과 Add to targets 옵션을 확인한다.


필요 프레임워크 추가

 TARGETS에서 프로젝트를 선택하고, "Build Phases" 탭의 "Link Binary with Libraries"에 필요한 프레임워크들을 추가한다. 추가해야하는 프레임워크는 다음과 같다.
  • AVFoundation.framework
  • CoreMedia.framework
  • CoreVideo.framework
  • QuartzCore.framework
  • libiconv.dylib



바코드 리더 뷰 추가

 새 UIViewController 클래스를 프로젝트에 추가한다. 이 클래스는 실제 바코드 리더로 사용된다. 여기서는 가장 기본적인 바코드 리더 기능만 구현하기 때문에, ZBarReaderViewController 클래스를 상속받는 것 외에는 별도의 뷰와 코드가 필요가 없다. 바코드 리더 UI를 커스터마이징하려면 이 컨트롤러를 수정해야 한다.

BarcodeReaderController.h
#import <UIKit/UIKit.h>
#import "ZBarReaderViewController.h"

@interface BarcodeController : ZBarReaderViewController

@end



바코드 리더 뷰 호출하기

 ViewController에는 바코드 리더 뷰를 불러올 버튼과 바코드 정보를 처리할 Delegate 메서드가 추가된다. ZBarReaderViewController.h 헤더 파일을 추가하고 ZBarReaderDelegate 프로토콜을 추가한다.

ViewController.h
#import <UIKit/UIKit.h>
#import "ZBarReaderViewController.h"

@interface ViewController : UIViewController <ZBarReaderDelegate>

@end


닙 파일을 통해 버튼과 IBAction을 추가하는 대신 동적으로 버튼을 하나 추가하고 메시지를 하나 선언한다.

ViewController.m
#import "ViewController.h"
#import "BarcodeController.h"

@interface ViewController ()

- (void)scan:(id)sender;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    self.view.backgroundColor = [UIColor whiteColor];
    
    UIButton *scanButton = 
        [UIButton buttonWithType:UIButtonTypeRoundedRect];
    [scanButton addTarget:self
                   action:@selector(scan:)
         forControlEvents:UIControlEventTouchUpInside];
    [scanButton setTitle:@"SCAN" forState:UIControlStateNormal];
    scanButton.frame = CGRectMake(20.0, 40.0, 280.0, 35.0);
    
    [self.view addSubview:scanButton];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
}

@end

버튼을 터치하면 BarcodeController가 모달 뷰로 표시된다. 실제 카메라를 사용하므로 시뮬레이터 대신 실제 기기를 통해 실행시켜야 한다.

ViewController.m
#pragma mark - Private methods

- (void)scan:(id)sender
{
    BarcodeController *barcodeController = 
        [[[BarcodeController alloc] init] autorelease];
    barcodeController.readerDelegate = self;
    
    [self presentViewController:barcodeController
                       animated:YES
                     completion:nil];
}


바코드 정보 가져오기

 BarcodeController에 추가로 ZBarReaderDelegate 프로토콜 메서드들을 구현한다. 바코드가 읽히면 imagePickerController:didFinishPickingMediaWithInfo: 메시지가 실행되고 NSDictionary 타입으로 인식된 바코드의 정보를 전달한다.

ViewController.m
#pragma mark - ZBarReaderController methods

- (void)imagePickerController:(UIImagePickerController *)picker
didFinishPickingMediaWithInfo:(NSDictionary *)info
{
    id<NSFastEnumeration> scanResults =
        [info objectForKey:ZBarReaderControllerResults];
    
    NSString *result;
    
    ZBarSymbol *symbol;
    for (symbol in scanResults)
    {
        result = [symbol.data copy];
        break;
    }
  
    NSLog(@"Result : %@", result);
    
    [result release];
    
    [self dismissViewControllerAnimated:YES completion:nil];
}

- (void)imagePickerControllerDidCancel:
    (UIImagePickerController *)picker
{
    [self dismissViewControllerAnimated:YES
                             completion:nil];
}


2013년 1월 3일 목요일

[iOS] iPhone 5 확인 매크로

아이폰5 덕분에 개발자가 신경써야 할 일들이 늘었다. 새삼 안드로이드 개발자분들이 존경스러워진다. 아래 코드는 아이폰5를 구분하는 매크로다. 출처는 스택오버플로.
#define IS_IPHONE ( [[[UIDevice currentDevice] model] isEqualToString:@"iPhone"] )
#define IS_IPOD   ( [[[UIDevice currentDevice ] model] isEqualToString:@"iPod touch"] )
#define IS_HEIGHT_GTE_568 [[UIScreen mainScreen ] bounds].size.height >= 568.0f
#define IS_IPHONE_5 ( IS_IPHONE && IS_HEIGHT_GTE_568 )

2012년 12월 13일 목요일

[OSX] 숨긴 폴더 및 파일 표시

터미널을 통해 다음 명령어를 실행한다.
defaults write com.apple.Finder AppleShowAllFiles YES

Finder 강제 종료 후 재실행이 필요하다.

[iOS] Volume Purchasing for Business Program


 어제, 미국에 한해 iOS 앱을 카피 단위로 판매할 수 있는 Volume Purchasing for Business Program(VPP)이 새로 발표됐다. 볼륨 앱 스토어를 통해 2~1000 Copy 단위(Volume)로 앱을 판매/구매할 수 있으며, 구매한 앱은 리딤 코드를 사용하여 앱스토어를 거치지 않고 자체적으로 배포가 가능하다. 아직은 미국 기업에 한정되어 있지만, 곧 한국에도 적용이 될 것이라 생각한다. 개인적인 차원에서도, 신규 사업분야 진출의 일환으로 모바일 사업을 준비 중인 팀 차원에서도 여러모로 큰 도움이 될 것 같다.

* 2013년 1월 현재, 미국 뿐 아니라 호주, 캐나다, 프랑스 등 10개 국가로 확대되었습니다.

[iOS] 루트 뷰 컨트롤러 표시하기

popToRootViewControllerAnimated: 메시지를 이용하면 루트 컨트롤러를 바로 표시할 수 있다.
[self.navigationController popToRootViewControllerAnimated:YES];


미리 알았다면 이런 짓은 안했을텐데.
for (int i = 0; i < [self.navigationController.viewControllers count]; i++) {
     [self.navigationController popViewControllerAnimated:NO];
}

[ASP.NET MVC] IValidatableObject 개체 테스트하기

IValidatableObject 인터페이스를 이용하여 유효성 검사를 수행하는 객체는 Validator 클래스를 통해 테스트할 수 있다. 다음은 테스트 주도 ASP.NET MVC 프로그래밍 책의 예제를 개선한 코드들이다. 참고로 책의 예제에서는 유효성 검사를 위해 커스텀 인터페이스를 사용하고 있다.

public class Todo : IValidatableObject
{
    //[Required]
    //[StringLength(25)]
    public string Title { get; set; }

    public IEnumerable<ValidationResult> Validate(
        ValidationContext validationContext)
    {
        if (string.IsNullOrEmpty(Title))
            yield return new ValidationResult(
                "제목이 입력되지 않았습니다.", 
                new[] { "Title" });

        if (Title != null && Title.Length > 25)
            yield return new ValidationResult(
                "제목이 25자를 초과할 수 없습니다.", 
                new[] { "Title" });
    }
}


제목 길이에 따른 각 개체들의 유효성 검사는 다음과 같이 테스트 한다.
[TestFixture]
public class TodoTest
{
    [Test]
    public void Title_Length_Should_Be_To_Maximum_Of_25_Characters()
    {
        // Arrange
        Todo longTodo = new Todo { Title = "123456789ABCDEF123456789ABCDEF" };
        Todo twentyFiveCharacterTodo = new Todo { Title = "123456789ABCDEF1234567" };
        Todo shortTodo = new Todo { Title = "123456789" };

        // Assert
        Assert.IsFalse(IsValid(longTodo));
        Assert.IsTrue(IsValid(twentyFiveCharacterTodo));
        Assert.IsTrue(IsValid(shortTodo));
    }

    private bool IsValid(IValidatableObject toValidate)
    {
        return Validator.TryValidateObject(
            toValidate, 
            new ValidationContext(toValidate, null, null), 
            null, 
            true);
    }
}


에러 메시지를 확인하고 싶다면 ValidationResult 리스트를 사용하면 된다.
[Test]
public void Title_Length_Should_Be_To_Maximum_Of_25_Characters()
{
    // Arrange
    Todo longTodo = new Todo { Title = "123456789ABCDEF123456789ABCDEF" };

    var result = new List<ValidationResult>();

    Validator.TryValidateObject(
        longTodo,
        new ValidationContext(longTodo, null, null),
        result,
        true);
        
    // Assert
    Assert.AreEqual(1, result.Count);
    Assert.AreEqual("제목은 25자를 초과할 수 없습니다.", result[0].ErrorMessage);
}

2012년 12월 12일 수요일

[ASP.NET MVC] ASP.NET MVC4 테스트 프로젝트에서 MVCContrib 사용 시 예외 발생 해결

ASP.NET MVC4  + MVCContrib 프레임워크를 사용하는 프로젝트들을 테스트하면 MVCContrib의 몇 몇 확장 메서드들이(ShouldMapTo<T>(), AssertActionRedirect<T>()와 같은) 이상한 예외들을 발생시킨다. 동일한 두 개의 타입을 틀린 타입으로 취급한다.

MvcContrib.TestHelper.ActionResultAssertionException : Expected result to be of type RedirectToRouteResult. It is actually of type RedirectToRouteResult.

MVCContrib3에서 참조되는 System.Web.Mvc.dll의 버전(1.0 ~ 3.0)이 테스트 프로젝트에서 참조된 버전(4.0)과 다르기 때문에 발생하는 문제다. App.config를 통해 1.0 ~ 3.0 버전을 강제로 4.0으로 바인딩하여 해결한다. 테스트 클래스 프로젝트에 App.config를 추가하고 다음과 같이 입력해준다.

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <runtime>
        <assemblyBinding xmlns="urn:schemas-microsoft-com:asm.v1">
            <dependentAssembly>
                <assemblyIdentity name="System.Web.Mvc" publicKeyToken="31bf3856ad364e35" />
                <bindingRedirect oldVersion="1.0.0.0-3.0.0.0" newVersion="4.0.0.0" />
            </dependentAssembly>
        </assemblyBinding>
    </runtime>    
</configuration>