2013년 3월 20일 수요일

[iOS] UIWebView를 이용한 로컬 HTML 파일 표시

 iOS 앱 내부에서 웹 페이지를 통해 정보를 전달하려면 UIWebView를 사용하면 된다. URL만 전달하면 UIWebView가 알아서 간단하고 멋지게 웹 페이지를 표시해준다. 그럼, 조건을 하나 추가해서 인터넷이 안되는 오프라인 상태에서도 웹 페이지를 표시하려면 어떻게 해야될까? 답은 간단하다. 필요한 파일들을 전부 앱에 포함시키면 된다. HTML, 자바스크립트, 스타일시트나 이미지 할 것 없이 모두 앱에다 추가하고 UIWebView를 사용하면 된다.

 아래 샘플 프로젝트는 트위터의 부트스트랩(Bootstrap) 프레임워크와 jQuery를 사용하는 간단한 웹 사이트를 UIWebView를 통해 표시하고 있다. 텍스트들은 아무 의미가 없고, Learn more 버튼을 터치하면 다른 페이지로 이동하고 Alert은 알림을 표시하며 Reload는 화면을 새로 고친다. 온라인/오프라인 가리지 않고 잘 작동한다.


상대 경로를 이용한 링크

 특별한 기술이 필요한 건 아니다. 앱에 포함시킬 html 페이지에서 참조되는 모든 로컬 파일들은 상대 경로로 지정한다는 사실만 기억하면 된다. 하위 폴더의 파일에는 "폴더 명/"을, 상위 폴더의 파일에는 ".../" 를 붙여준다. 샘플 프로젝트의 www 폴더에는 두 개의 html 파일과 부트스트랩 폴더가 포함되어 있다. index.html을 살펴보면 알겠지만 각 파일의 경로가 상대 경로를 사용한다는 것을 제외하면 웹 서버에 호스팅되는 일반적인 html 파일과 다를 게 없다.(호스팅되는 html 파일들도 상대경로를 쓴다..-_-;)

새 프로젝트 생성 및 폴더 추가

Single View Application 템플릿을 이용해 새 iOS 프로젝트를 생성한다. 기본으로 생성되는 ViewController 클래스에 UIWebView 아울렛을 추가하고 UIWebViewDelegate 메시지들을 추가해준다. NIB 파일에도 UIWebView를 추가하고 아울렛과 델리게이트를 지정해준다.

프로젝트 네비게이터에서 프로젝트를 선택하고 우 클릭, "Add Files to ..."를 선택한다. 준비한 www 폴더를 선택하고 Destination에 체크, Folders 옵션은 "Create folder references for any added folders"를 선택한다. Add to targets도 체크한다. 프로젝트에 파란색 www 폴더가 추가된다.

UIWebView 설정

viewDidLoad 메시지에 index.html를 요청하는 코드를 추가한다. 번들에 포함된 html 파일의 경로를 이용한 NSURLRequest 객체를 생성해야 한다. 파일 경로 지정 시 추가한 디렉터리 명을 지정해줘야 한다. 기억해 두자.


하이브리드 애플리케이션?

샘플 앱의 화면을 스타일시트를 통해 iOS 기본 UI와 완벽히 똑같이 변경했다고 상상해보자. 사용자가 네이티브 앱 화면인지 웹 화면인지 구분을 할 수 있을까? 실제 PhoneGap과 같은 대다수의 하이브리드 앱 개발 프레임워크들이 이런 방식을 사용한다. 샘플 프로젝트와 원리와 동일하다. HTML/CSS로 이루어진 뷰와 네이티브 코드로 이루어진 컨트롤러 사이를 웹 엔진(= UIWebView)가 연결해주는 방식이다. 샘플이 너무 간단해서 믿기지 않겠지만 사실이다. 샘플 앱에서 "Reload" 버튼을 터치하면 네이티브 앱이 어떤 식으로 반응하는지 확인해보길 바란다.



2013년 3월 15일 금요일

Windows Azure SQL데이터베이스 연결을 위한 NHibernate 설정

Azure 관리자 콘솔에서 알려주는 연결 문자열로는 연결 안된다는게 함정.

hibernate.cfg.xml

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 )