반응형
블로그 이미지
개발자로서 현장에서 일하면서 새로 접하는 기술들이나 알게된 정보 등을 정리하기 위한 블로그입니다. 운 좋게 미국에서 큰 회사들의 프로젝트에서 컬설턴트로 일하고 있어서 새로운 기술들을 접할 기회가 많이 있습니다. 미국의 IT 프로젝트에서 사용되는 툴들에 대해 많은 분들과 정보를 공유하고 싶습니다.
솔웅

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

글 보관함

카테고리


반응형
지난번에 올린 Tutorial 인 In App Purchase on Android 이전에 나왔던 새로운 기능에 대한 튜토리얼이 있었습니다.
그 주에 바빠서 정리를 못했는데 너무 delay 되다 보면 다루지 못하고 넘어갈 것 같아 서둘러 정리합니다.

앱을 만들다 보면 애니메이션을 사용할 때도 있고 꼭 애니메이션을 사용하지 않아도 많은 이미지 파일을 사용하게 됩니다.
disk에서 이미지를 불러와서 rendering 하고 화면에 display 하는 과정은 device의 resource를 많이 사용하는 아주 expensive 한 작업입니다.
너무 많은 이미지 사용은 앱의 용량도 크게하고 퍼포먼스에도 안 좋은 영향을 줍니다.

이번에 Corona에서 image sheet라는 새로운 API를 제공하면서 이러한 이미지 처리를 효율적으로 할 수 있는 방법을 제공하고 있습니다.

저도 Fire Man (Fire fighter)  앱이 너무 많은 애니메이션 기능 사용으로 아이폰 3GS나 구식 안드로이드 폰에서는 퍼포먼스가 아주 느려서 고민이 많았거든요.

다음 앱을 개발할 때는 이 image sheet를 사용해서 퍼포먼스에 문제가 없도록 해야 할 것 같습니다.



Corona build 2012.759 버전에 그래픽 관련 새로운 기능이 추가 됐습니다.
오늘은 이 기능에 대해 알아보겠습니다.

아주 많은 부분이 다뤄질 예정이니 주의를 집중해서 보세요. 조금 어렵기도 합니다. 아마 중급정도 수준의 튜토리얼이라고 할까요? 오늘 다룰 부분 중에는 이전의 Corona Doc나 튜토리얼에 나왔던 개념이 아닌 전혀 새로운 개념들도 있습니다.
오늘의 세가지 주요 topic은 아래와 같습니다.
- Image Sheets (새로운 기능)
- Image Groups (새로운 기능)
- Sprite (완전히 바뀜)

아래 다뤄지는 기능은 Corona sdk build 2012.759 버전 이상에서만 작동 됩니다. 그리고 build 2012.761 버전 이상을 사용하실 것을 권장합니다. 오늘 소개할 기능과 관련돼 약간 수정된 내용이 761버전 이후 부터 적용됐거든요.

Notes on Performance

Graphics의 많은 부분이 바뀌었고 그것과 더불어 performance 향상을 제공합니다. 특히 iPhone 3GS 같은 조금 오래된 기계에서 퍼포먼스가 안좋았던 부분이 많이 개선 됐습니다. 하지만 개발자로서 항상 유념해야 될 것은 잘 구성된 코드가 퍼포먼스 향상의 가장 기본이라는 것입니다.

Image Groups 같은 경우에는 어느 부분에서는 좋은 점을 제공하지만 또한 어느 부분에서는 사용하는데 제한이 있을 수 있습니다.
이 새로운 기능들을 사용할 때 이 잇점과 제한된 점을 잘 생각해서 best 한 선택을 하셔야 합니다.

그리고 또 하나의 좋은 소식은 최근의 코로나 버전에서 다른 방법으로 퍼포먼스 향상 효과를 주었는데요. 그것은 바로 화면 밖에 있는 객체에 대해서는 렌더링이 일어나지 않도록 만들었다는 것입니다. 이 방법은 따로 코딩을 바꾸실 필요는 없습니다. 그냥 최근 버전의 코로나를 다운 받으셔서 새로 빌드 하시면 됩니다.

Image Sheets

이 API는 이번에 새로 나온 기능 입니다. 전문 용어에 익숙하신 분은 texture atlas  기능이라고 하면 이해하시기 쉬우실 겁니다.

이해를 돕기위해 좀 은유적으로 설명을 드리자면 여러분 앱의 모든 이미지들이 한장의 종이에 있고 그 종이의 부분 부분을 가져와서 한개의 이미지처럼 사용할 수 있도록 합니다. 그러면 메모리를 줄일 수 있고 이미지 렌더링 시간도 줄일 수 있어서 큰 퍼포먼스의 향상을 가지고 올 수 있습니다.

disk에서 이미지를 메모리로 불러와서 그것을 렌더링 해서 앱에 표시하는 작업은 아주 비싼(에너지 소비가 많은) 작업입니다. 그래서 각각의 이미지별로 이 작업을 하도록 하기 보다는 여러 이미지를 하나의 이미지화 해서 이런 비싼 작업을 한번만 할 수 있도록 하는게 기본 개념입니다.

Texture Size Limit

한 개의 이미지 파일에 몇개의 실제 이미지를 넣을 수 있는지에 대한 제한은 없다. 하지만 이 부분은 유념해야 한다. 각 디바이스 마다 최대 texture size의 제한이 있다. 한개의 이미지 파일의 width,height에 대한 픽셀 수에 대한 제한값이다. 그러므로 한 이미지 파일안에 몇개의 실제 이미지가 들어가는지는 제한이 없지만 이미지 파일  크기가 너무 크면 사용할 수 없다. (그러면 이론적으로는 디바이스가 허용하는 최대크기의 이미지 파일의 픽셀 수가 최대 하용할 수 있는 이미지 갯수겠네요. 1픽셀짜리 이미지를 사용한다면.. 실제 그렇지는 않겠지만요.. 그러면 그건 이미지가 아니라 그냥 color가 있는 점이겠죠?  그냥 심심해서 상상해 봤어요... ^^)

아래 코드는 해당 디바이스에서 허용하는 texture 의 최대사이즈를 구하는 방법입니다.

print( system.getInfo( "maxTextureSize" ) )

대개 코로나가 지원하는 디바이스들은 허용 사이즈가 아주 크다는 겁니다. 여러분 앱에 수백개의 이미지 객체들이 들어간다고 하더라도 몇 개의 이미지 sheet만 사용해도 충분히 해결이 될 겁니다.

Wait, I’m already doing this!

여러분들 중에는 이전의 Sprite API를 사용해서 이러한 기능들을 이미 사용해 보신 분들도 많이 계실 겁니다. 이전의 Sprite API에서는 애니메이션을 사용하기 위한 기능을 주로 지원했습니다. 그래서 애니메이션이 아닌 정적인 이미지를 만들려면 불필요한 부분들이 많이 있습니다. 메모리를 절약하기 위해 이 Sprite API를 사용한다는 건 좀 부담스러운 일이죠.

그래서 이 image sheet를 지원하게 됐습니다. 이 image sheet API를 사용하면 훨씬 더 효율적으로 그리고 쉽게 사용할 수 있습니다. 이 API를 사용하면 정적인 이미지와 애니메이션 모두에서 사용하실 수 있습니다.

보너스가 한가지 더 있는데요. image sheet는 해상도별로 이미지를 다이나믹하게 지원해주는 기능이 있습니다. (예: retina graphics) 이전의 sprite API에서는 이 같은 기능을 구현하기 위해서는 조금 복잡하게 해야 합니다. 이 의미는 이 기능을 사용하면 이번에 해상도가 아주 높아져서 나온 새 애플(Apple)의 아이패드(New iPad) 에도 자동적으로 이미지가 맞게 지원된다는 겁니다.

How to Use Image Sheets

이제 이 image sheets를 어떻게 사용하는지 알아보겠습니다. 우선 신택스부터 보겠습니다. 그리고 나서 display.newImage()와 display.newImageRect()와 함께 어떻게 image sheets가 사용되는지 알아보는 예제를 다뤄보겠습니다. 이 image sheets를 image group과 sprite animation과 함께 사용 하실 수도 있습니다.

Image Sheet Syntax:
graphics.newImageSheet( filename, [baseDir, ] options )

아주 간단하죠? 파라미터 filename은 실제 image file의 이름이구요. baseDir은 디렉토리 입니다. (system.ResourceDirectory 가 될 수도 있고 아니면 다른 곳이 될 수도 있죠.) options 파리미터는 필수로 이 image sheet의 프레임에 대한 데이터를 가지고 있는 테이블을 가리킵니다.

혼란을 방지하기 위해 image는 image sheet file로 graphics.newImageSheet()로 불러온 파일을 말하고 frame은 그 image sheet안의 각각의 이미지들을 구분해서 말하고 있습니다.

options

option 테이블에는 세가지 케이스가 있을 수 있습니다.

    Simple: image sheet의 모든 frame들이 각각 같은 width와 height를 가질 경우
    Complex: image sheet의 frame들이 각각 다른 width나 height를 가질 경우
    Old-Style:  sprite API 데이터 포맷과 같은 방법입니다. (이전에는 sprite.newSpriteSheetFromData() 함수를 사용했었습니다.)

old style은 이제는 권장하지 않습니다. simple이나 complex 케이스 중 하나만 선택하시면 됩니다. old style에 대해서는 따로 다루지 않겠습니다. 이 케이스가 있는 이유는 이것을 이용하던 다른 third-party 툴들이 있기 때문에 아직 없애지 않은 겁니다.

Simple Example

예제를 하나 소개해 드리겠습니다. 우선 image sheet를 로딩할 때 simple option을 사용하는 방법을 보죠. 다시한번 언급하면 image sheet 내의 모든 frame이 각각 같은 width와 height를 가지고 있는 경우 이 simple option을 사용합니다.

local options =
{
    -- The params below are required
   
    width = 70,
    height = 41,
    numFrames = 2,

    -- The params below are optional; used for dynamic resolution support

    sheetContentWidth = 70, -- width of original 1x size of entire sheet
    sheetContentHeight = 82 -- height of original 1x size of entire sheet
}

local imageSheet = graphics.newImageSheet( "fishies.png", options )

예제에서 보시듯이 simple case option은 진짜로 simple 합니다. options에 들어가는 파라미터는 width,height,numFrames 이렇게 3개밖에 없습니다. 남은 2개의 optional 파리미터들은 dynamic resolution images를 사용할 경우 필요한 겁니다. (예를 들어 retina graphics 를 사용하기 위한 image sheet의 @2x 버전 같은)

Complex Example

image sheets에 여러 다른 객체들을 넣어서 사용할 경우 complex-case option 테이블을 사용해야 합니다. 이 경우 각각의 frame에 대해 정해줘야겠죠.

local options =
{
    -- array of tables representing each frame (required)
    frames =
    {
        -- FRAME 1:
        {
            -- all params below are required for each frame
            x = 2,
            y = 70,
            width = 50,
            height = 50
        },
       
        -- FRAME 2:
        {
            x = 2,
            y = 242,
            width = 50,
            height = 52
        },
    },

    -- optional params; used for dynamic resolution support
    sheetContentWidth = 1024,
    sheetContentHeight = 1024
}

local imageSheet = graphics.newImageSheet( "imageframes.png", options )

위 예제를 보시면 image sheet는 두개의 frame을 가지고 있습니다. simple case와는 조금 다르죠? 왜냐하면 각각의 frame들을 array로 그 값을 지정해줘야 하기 때문입니다. image sheet의 complex-case option 테이블을 이용할 경우 이 예제를 참고로 작성하시면 됩니다.

Using with Existing Image Functions

다음으로는 image sheets로부터 얻어낸 각각의 객체들을 기존의 함수들인 display.newImage()와 display.newImageRect()을 가지고 어떻게 사용해야 하는지를 알아보겠습니다.

일단 image sheet를 이용해서 이미지 객체를 이미 만들었다고 가정한 후 아래 예제를 살펴 보세요.
-- assumes 'options' is already constructed (simple or complex cases)
local imageSheet = graphics.newImageSheet( "myimagesheet.png", options )

-- display.newImage()
--
-- SYNTAX:
-- display.newImage( [parent ,] sheet, frameIndex )

local fish = display.newImage( imageSheet, 2 )
fish.x, fish.y = 100, 100

-- display.newImageRect()
--
-- SYNTAX:
-- display.newImageRect( [parent ,] sheet, frameIndex, width, height )

local frog = display.newImageRect( imageSheet, 4, 40, 82 )
frog:setReferencePoint( display.TopLeftReferencePoint )
frog.x, frog.y = 0, 10

보시다시피 일반적으로 display.newImage()나 display.newImageRect()함수를 사용하는것과 크게 다르지 않습니다. 다른 점은 image sheet를 사용할 경우 특정 filename 대신 해당 frame index number를 사용한다는 것입니다.

여러분들이 image sheet를 로딩할 때 simple을 사용했던지 complex를 사용했던지 상관없이 위 예제처럼 사용하시면 됩니다. (old-style을 사용해도 마찬가지 입니다.)

Power of 2 Dimensions

build 2012.264를 보시면 power of 2 에 대한 제한이 있었습니다. 지금은 이 image sheet를 사용하면서 이 제한이 없어졌습니다. 그 제한들은 width와 hight가 8, 16, 32, 64, 128, 256, 512, 1024, 2048 .... 사이즈에 부합되야 한다는 것이었습니다. 하지만 image sheets를 사용하면서 이런 제약은 없어졌습니다. 하지만 texture memory 관리 등을 생각하면 이 power of 2 에 대한 규칙을 지키는 것이 좋습니다.

Removing Image Sheets

image sheet를 remove하려면 image sheet를 사용하는 객체를 그냥 remove 하시면 됩니다. (image objects, sprites, image groups 등) 그리고 나서 image sheet를 nil로 선언하세요.

아래 예제가 있습니다.

-- obj1 and obj2 are using the image sheet
obj1:removeSelf()
obj1 = nil

obj2:removeSelf()
obj2 = nil

-- remove reference to the image sheet
imageSheet = nil


======= o =====  o ====== o ======== o ========

내용이 길어서 2회로 나눠 싣습니다.
다음 글에서는 Image Groups() , Sprites 등에 대해 다룰 겁니다.
반응형


반응형
코로나 SDK에서 안드로이드용 In-App Purchase 관련  API를 내 놨습니다.
이제 아이폰,안드로이드폰 모두에서 코로나로 개발한 In-App Purchase기능을 사용할 수 있게 됐습니다.

아래 이번주에 Corona SDK에서 배포한 안드로이드 In-app Billing 튜토리얼을 정리했습니다.

여러분도 참고하세요.

======o ===== o ===== o ===== o ===== o =====

Getting Started with Android In-app Billing

앱을 통해서 돈을 벌 수 있는 방법은 유료 앱으로 publish를 하던가 광고를 달던가 아니면 가상 머니를 사용하는 방법이 있습니다.
그 외에 in-App Billing이 있는데요. 이번에 Corona SDK에서 안드로이드용 In-App Billing 기능을 발표했습니다.

이 in-App Purchase 기능은 모바일 앱을 이용한 많이 애용되는 비지니스 모델인데요 이것을 다른 말로 freemium 이라고 합니다. freemium은 일단 무료로 제공하고 그 외의 고급 기능이나 아이템을 유료로 구매할 수 있도록 하는 것을 말합니다.
모바일 앱의 경우는 다음 레벨로 가거나 특별한 아이템을 구한다던가 virtual currency를 사용하던가 하는 기능을 넣을 수가 있을 겁니다.

유저가 구매를 approve하면 새로운 컨텐츠가 unlock 되거나 외부 소스로부터 다운로드 되던가 할 겁니다.
이 방법을 사용하면 아주 다양한 방법의 수익 모델을 만들어 낼 수 있습니다. 그리고 이번에 Corona SDK에서 안드로이드 용으로 이 기능을 지원했기 때문에 그 다양한 수익 모델을 Corona SDK로 개발하면서도 사용할 수 있습니다.
아이폰 앱에는 이미 이 기능이 지원됐기 때문에 코로나 개발자들은 두개의 시장에서 이 기능을 이용할 수 있게 되겠죠? 이것이 Corona SDK 같은 cross-platform development tool의 매력이라고 할 수 있을 겁니다.

하지만 이 좋은 기능도 개발자나 기획자가 사용할 줄 모르면 아무 소용이 없을 겁니다. 이 기능을 이용하려면 최신버전의 Corona SDK가 있어야 합니다. (현재는 유료 사용자만이 다운 받을 수 있습니다.)



Getting Started

시작하기 전에 여러분이 준비해 두셔야 할 부분이 있습니다.
1. Corona SDK build 2012.769 이후 버전
2. Indie-AndroidCorona Pro 유료 사용자 일 것
3. Android Developer 계정을 가지고 있을 것

1. Android Deveoper Console
안드로이드 내에서 billing이 가능하게 하려면 BILLING 퍼미션이 build.settings에 세팅 돼 있어야 합니다. 그리고 이것을 Android Developer Console에 없로드해서 당신의 앱이 In-App Billing Products를 시작할 수 있도록 만들어 두어야 합니다.
main.lua에 아무런 코딩이 안 돼 있어도 이 작업을 해야 합니다. 그리고 지금 개발이 진행 중이라도 상관없습니다. 어쨌든 우선 이 step을 밟아야 합니다. 여러분의 앱에 In-app 상품을 넣을 수 있으려면 우선 이 과정을 거쳐야 하는게 구글이 만들어 놓은 룰입니다.
당신의 앱이 이미 판매중이거나 마켓에 오픈돼 있는 상태라도 이 기능을 넣으려면 우선 build.settings에 퍼미션을 넣고 업로드 한 다음에 In-App purchase 기능을 개발해야 합니다.
한번 이렇게 해 두면 Android developer console의 해당 앱 밑에 in-app products라는 링크를 클릭해서 이곳에서 올린 in-app 상품을 보실 수가 있습니다.

build.settings에 BILLING 퍼미션을 넣는 방법은 아래와 같습니다.

settings =
{
    orientation =
    {
        default = "landscapeRight",
    },

    android =
    {
        usesPermissions =
        {
            "com.android.vending.BILLING",
        },
    }
}

이 예제에서 주의깊게 보셔야 할 점은 android - usesPermissions의 com.android.vending.BILLING 부분 입니다.
이 부분은 orientation을 지정한 settings 다음에 위치해 있습니다.
 
이렇게 하신 후에 android 앱으로 build하세요. 그리고 그 apk화일을 android developer console에 업로드 하시면 됩니다.

아직까지는 Publish를 하지는 마세요.

그렇게 한 다음부터 In-app 제품을 넣기 위한 Administering In-app Billing Guide를 진행 하실 수 있습니다.

안드로이드 개발자로서 마켓에 앱을 올려보신 분들은 아시겠지만 앱을 다 개발 한 다음에 publish를 해야하는데요. 요즘은 이것을 또 Activate 하는 단계도 생겼더라구요.
혹시 나중에 앱을 다 만들고 나서 마켓에 올리고 테스트 할 때 주의하세요.

Google Documentation

구글 Guide에 대해서는 위에 링크를 걸어 놓기는 했지만 여기에 대해서는 한번 언급하고 넘어가는게 좋을 것 같습니다.

구글에서는 In-app Products를 어떻게 셋업해야 하는지에 대해 꽤 좋은 가이드를 제공하고 있습니다. 그리고 그것을 쉽게 테스트 할 수 있도록 했구요.
만약 여러분이 Android Developer Console쪽에서 어떻게 세팅해야 하는지를 자세히 알고 싶으시면 아래 두 가이드를 참고하세요.

2. The Corona "store" API

이 튜토리얼에서는 구글에서 제공하는 test product identifier들을 사용하겠습니다.
이 테스트 상품에서는 아래 세가지 Action을 가져올 수 있습니다.

- android.test.purchased : 구매가 성공했을 때 얻어 옴
- android.test.canceled : 구매 트랜잭션이 cancel 됐을 때 얻어 옴
- android.test.item_unavailable : 해당 제품 구매가 가능하지 않을 때 얻어 옴

이 테스트 상품의 진짜 nice 한 점은 이렇게 각각 다른 상황에서 여러분들이 이를 어떻게 쉽게 콘트롤 할 수 있을 지 경험할 수 있게 해 준다는 점 입니다. 예를 들어 여러분은 android.test.canceled 상품을 이용해서 유저가 트랜잭션 중 cancel했을 경우에 대한 코딩을 할 수 있게 됩니다.

store.init("google", transactionCallback)

여러분이 코딩을 하면서 이 in-app purchase 기능을 구현하기 위해서는 제일 처음 store.init() API를 사용하게 될 겁니다. 파라미터로는 구글 마켓(얼마전 Google Play로 바뀌었습니다.)을 사용할 거라는 것을 알리는 "google"을 첫번째로 넣고 두번째로는 불러올 함수 명을 넣습니다.

아래 transaction callback listener 함수와 함께 store.init()을 사용하는 방법에 대한 샘플 예제가 있습니다.

local function transactionCallback( event )
    local transaction = event.transaction

    if transaction.state == "purchased" then
            -- Transaction was successful; unlock/download content now
   
    elseif transaction.state == "restored" then
           -- You'll never reach this transaction state on Android.

    elseif transaction.state == "refunded" then
        -- Android-only; user refunded their purchase
        local productId = transaction.productIdentifier
        -- Restrict/remove content associated with above productId now
  
    elseif transaction.state == "cancelled" then
        -- Transaction was cancelled; tell you app to react accordingly here

    elseif transaction.state == "failed" then
            -- Transaction failed; tell you app to react accordingly here
     end

    -- The following must be called after transaction is complete.
    -- If your In-app product needs to download, do not call the following
    -- function until AFTER the download is complete:

    store.finishTransaction( transaction )
end

store.init( "google", transactionCallback )

맨 아래 store.init()이 transactionCallback 함수를 call 하는 보분이 있죠? 이 부분이 Android In-app Billing 이벤트가 발생 했을 때 실행 될 부분입니다.

Handling Refunds

Android In-app billing은 iOS의 In-app billing과 거의 같은데 한가지 refunded라는 트랜잭션 상태가 더 있습니다. iOS와는 다르게 안드로이드 플랫폼에서는 유저가 refund할 수 있도록 해 줍니다. 그래서 유저에게 주었던 contents를 다시 가져오려면 바로 이 refunded 트랜잭션 상태일 때 해야 할 것입니다.

위 샘플에서 보듯이 refunded 트랜잭션 상태에서 받는 중요한 데이터는 event.transaction.productIdentifier입니다. 이 정보를 가지고 여러분은 필요한 작업을 하실 수 있습니다. 아마도 그 필요한 작업은 환불 했을 경우 해당 content를 다시 block 시키는 거겠죠? 아니면 돈을 내고 추가로 다운 받은 파일을 지우는 기능이던가요.

No "restored" State on Android

store.restore() 함수는 로그인 된 유저 계정과 연관된 product id의 리스트를 다시 검색할 수 있도록 해 줍니다. iOS에서는 이 product 리스팅이 transactionCallback listener 의 restored 트랜잭션 상태에서 받을 수 있습니다. 그리고 안드로이드에서는 이 restored 트랜잭션 상태가 없습니다. 대신에 그 상품이 purchased를 다시 실행하면서 그 기능을 할 수 있습니다.
아주 작은 차이이지만 transactionCallback listener 함수를 디자인할 때 잘 고려해서 디자인을 해야 합니다.

Other Transacton Event Data

transaction events와 관계된 다른 모든 데이터들은 iOS의 In-app purchase와 동일합니다. 그러니까 다음 guide를 꼭 봐 주세요.
In-app  Purchase Guide
Transaction Listener Callback Events
그리고 예전에 in-Purchase app 샘플을 분석했던 글을 보시면 도움이 되실겁니다.
코로나 네트워킹 과 웹 서비스 3 (In App Purchase 1)

store.finishTransaction (transaction)

위 샘플 코드에 있는 transactionCallback() 함수에 있는 모두 아주 중요한 것들입니다.
store.finishTransaction은 모든 트랜잭션의 마지막부분에 반드시 call 되야 합니다.
어떤 트랜잭션이 끝났다는 것을 확실히 정의하는 부분입니다.
만약 어떤 파일을 다운로드 받아야 할 때는  어떤 상황이 발생할 까요? 아마도 이 다운로드가 모드 끝나고 나서 store.finishTransaction()이 call 되어야 할 겁니다. (그러려면 어딘가에 network.download() 리스너가 있어야 겠죠.)

만약 이미 코딩은 최초 다운받은 앱에 다 돼 있고 단순히 어떤 content를 unlock 하는 기능만 수행될거면 callback 리스너 마지막 부분에 store.finishTransaction()을 넣으면 되겠죠.

두 경우 모두 첫번째 인수로 event.transaction 테이블을 넘겨줘야 한다는 것을 잊지 마세요.
 store.finishTransaction( event.transaction )

3. Purchasing Products

이제 purchases, refunds, failed/cancelled 같은 트랜잭션들을 어떻게 다뤄야 할 지 알아 보겠습니다. iOS in-app purchase에서와 같이 store.purchase()를 사용해서 트랜잭션을 초기화 합니다. 대개 앱 화면에서 Buy 버튼을 누르면 이벤트 리스너가 감지해서 call할 때 이 동작이 이뤄 집니다.
-- single product purchase
    store.purchase( { "android.test.purchased" } )

-- multi-item purchase
   store.purchase( { "android.test.purchased", "android.test.canceled" } )

여기서도 Google에서 제공하는 테스트 product들을 사용해서 테스트 해 볼 수 있습니다.
좀 더 자세한 정보를 보시려면 store.purchase() documentation 을 참고하세요.

Cross-Platform Development

코로나의 장점은 한번의 코딩으로  아이폰, 안드로이드 용 앱을 만들 수 있는 multi-platform 기능입니다. 이 기능을 좀 더 쉽게 콘트롤 할 수 있도록 코로나에서는 store API에 새로운 프로퍼티를 추가했습니다.
애플의 In-app Purchase는 iOS 디바이스들에서만 가능하고 안드로이드의 In-app Billing은 Google Play에서만 가능합니다. (같은 안드로이드 앱이라도 Nook나 Kindle Fire 에서는 In-app Billing을 사용할 수 없습니다.)
새로 제공되는 기능은 store.availableStores 테이블로 어떤 디바이스 인지를 알수 있는 파라미터 입니다.

아래 예제가 있습니다.

if store.availableStores.apple then
    store.init("apple", transactionCallback)
   
elseif store.availableStores.google then
    store.init("google", transactionCallback)
end

이렇게 코딩을 하면 아이폰용과 안드로이드 용 파일을 따로 관리할 필요가 없겠죠?

iOS의 In-app Purchases와 Android의 In-app Billing은 한가지 크게 다른 개념이 있습니다. 안드로이드에서는 각각의 In-App product들에 대해 정보를 retrieve할 수 없는 반면에 iOS에서는 이 기능이 가능합니다. 이것은 구글의 한계라고 볼 수 있겠죠. 이 의미는 상품의 이름, description같은 정보들을 직접 앱 내에서 혹은 외부 서버에서 개발자들이 직접 관리해야 한다는 얘기 입니다. (만약에 필요하다면요.)

이 다른 점 때문에 store.loadProducts() 함수가 있는데 이것은 iOS in-app purchases에서만 사용될 수 있습니다. 여러분이 iOS,안드로이드 두 플랫폼 모두에서 돌아가는 앱을 개발하고자 한다면 이 함수가 가능한지 여부를 체크하기 위해서 store.canLoadProducts 프로퍼티를 사용하실 수 있습니다. (store.init()에서 pass된 플랫폼이 무엇인가에 따라 true와 false를 return 하게 됩니다.)

 Additional Resources

Android In-app Billing 을 테스트 하시려면 구글에서 제공하는 테스트 상품으로 쉽게 테스트하실 수 있구요. 또 여러분이 만든 상품으로도 쉽게 테스트 하실 수 있습니다. 이번에 새로 제공되는 Corona SDK 내의 InAppPurchase 샘플 코드를 참조하세요.
/SampleCode/Networking/InAppPurchase 에 있습니다.

그리고 예전에 iOS in-app purchase와 관련해서 올려 놓은 In-app Purchase Guide도 보시구요. iOS용 가이드 이지만 대부분이 안드로이드에서도 적용 됩니다. 그리고 각각의 store API 함수에 대한 정보를 보시려면 In-app Purchase API Reference를 보시기를 권장합니다.

이 기능은 Corona Daily Build 2012.760 이후 버전에서만 가능합니다.
현재까지 이 버전은 유료 사용자만 받으실 수 있습니다.

======o ===== o ===== o ===== o ===== o =====

요즘 예전처럼 폭풍집필이 잘 안되네요.
슬럼프인가봐요.. 휴~~~~~~~
여러분의 추천이 큰 힘이 될 수 있을 것 같아요.
추천 부탁드려요.

추천.. 추천.. ~~~ ~~~ 감사.. 감사..
반응형


반응형

요즘 사무실 이사에 이런 저런 일들이 많이 생겨서 블로그 글 올리는 일이 뜸해졌습니다.

어느 정도 일이 정리 되면 다시 열심히 Study 해서 이 블로그에 정리 해 놓을께요. 그리고 댓글 올리신 분들도 많이 계신데 미처 답글을 드리지 못했습니다. 이번주에 질문 올리신 분들 제가 research 하고 직접 테스트 한 답을 구해서 답글 올릴께요.

오늘은 코로나에서 새로 나올 Corona Level Editor 에 대한 아이콘 콘테스트가 있어서 이 소식을 소개합니다.

아래 글은 Corona sdk를 만든 회사인 Ansca 의 CEO 인 Carlos 가 올린 글입니다.


===== o ===== o ===== o ===== o =====

Corona Level Editor Icon Challenge

Should I really explain what this is about?

이게 뭔지 일일이 설명해야 할 까요?

You seen our forthcoming level editor. Well… Guess what? Time to unleash the artist in you and we are going to hold the Level Editor Icon Challenge.

여러분 아마 조만간 나올 level editor를 보셨을 겁니다. 이제 뭔지 아시겠어요. 이제 여러분 몸속에 흐르는 예술인의 피를 맘껏 펼치실 때입니다. 코로나에서는 Level Editor 아이콘 콘테스트를 엽니다.

Submit a Corona Level Editor Icon by April 2nd. (See, if I would have put down April 1st, the US folks here would have thought this would have been an April’s Fools Joke. O el dia de los inoncentes)… Send me a PNG file at least 512×512 and then I will need the full PSD if we select your icon. (AI files works wonders too…)

코로나 레벨 에디터 아이콘을 4월 2일까지 응모해 주세요. (아마 내가 4월 1일이라고 얘기하면 여기 미국 애들은 이게 만우절 거짓말이라고 생각할 수도 있을거예요. 그래서 4월 2일로 했습니다.  O el dia de los inoncentes) 최소 512X512 크기의 PNG 파일로 보내주세요. 선택되면 PSD 원본을 제출해 주셔야 합니다. (모두 다 제대로 동작해야 겠죠?)

And, because I want to keep his a surprise, the only way you can enter is by e-mailing me your entries at my email address at  cicaza [at]@ anscamobile []dot[]. com. (protecting myself from spammers.)… (remove the [at] and the []dot[])

응모 방법은 cicaza [at]@ anscamobile []dot[]. com. (스팸 방지를 위해서 이렇게 쓴 겁니다. )... (여기서 [at] 하고 []dot[]을 빼시면 되요.)

Winner will get a free one year subscription to Corona PRO and or an extension to their subscription and some other Ansca goodies. ;=)

우승자는 Corona PRO 1년 사용권을 드립니다. 기존 사용자에게는 1년 연장해 드리구요. 그리고 다른 Ansca 제품들도 조금 더 준비 돼 있습니다.

The winning entry will have to give us all rights to the image. No prior/derivative or copies of images are to be entered. Once we select a winning entry I will notify you via email and then disclose publicly.

우승작품은 모든 사용 권한이 Ansca 에게 있게 됩니다. 다른 곳에 응모했거나 남의 작품을 보내주시면 안되구요. 당선작은 이메일로 알려드리고 또 공개도 할 겁니다.

Thanks

Carlos.

===== o ===== o ===== o ===== o =====

이상입니다.

아마 코로나 레벨 에디터에 대해 전혀 모르시는 분 계실테죠?

아래 이미지가 앞으로 나올 Corona Level Editor 화면이랍니다.

그림을 보니까 단순히 Level별 난이도를 조절하는게 아니라 Physics 적용이나 화면 배치 등 GUI 방식으로 어느정도 개발을 쉽게 해 주는 기능이 많은 것 같습니다.

지난번에 Corona sdk 책 표지 광고 디자인 응모전에서는 제 기억에 폴란드 사람이 우승하고 미국사람하고 멕시코 사람이 준우승해서 상품을 받은 것 같은데요.

이번엔 한국 분이 받으면 좋겠네요.

그래픽 디자인 가능하신 분 응모 하세요.

반응형

Sencha Touch 2 Tutorial - View - 02

2012. 3. 12. 21:54 | Posted by 솔웅


반응형
Using Views in your Applications 02

Custom Configurations and Behavior

Sencha Touch 2는 예측 가능한 API들을 제공하고 깔끔한 코드와 쉬운 테스트가 가능하도록 하하기 위해 configuration system에 extensive를 사용합니다. 개발자들에게도 클래스를 만들 때 같은 방식으로 만들기를 강하게 권장합니다.

이미지를 tap했을 때 이미지에 대한 정보가 팝업으로 뜨는 이미지 뷰어를 한번 만들어 봅시다. 우리의 목표는 이미지 url, title 그리고 description이 있을 수 있는 재사용 가능한 뷰를 만드는 것입니다. 그리고 이 이미지를 tap했을 때 title과 description을 표시해 줄 겁니다. 이 모든 기능을 재사용 가능하도록 하는 클래스를 만들어 보겠습니다.

이미지를 표시하기 위해서는 Ext.Img 컴포넌트를 사용해야 합니다. 아래처럼 subclass를 만들어 보겠습니다.

Ext.define('MyApp.view.Image', {
    extend: 'Ext.Img',

    config: {
        title: null,
        description: null
    },

    //sets up our tap event listener
    initialize: function() {
        this.callParent(arguments);

        this.element.on('tap', this.onTap, this);
    },

    //this is called whenever you tap on the image
    onTap: function() {
        Ext.Msg.alert(this.getTitle(), this.getDescription());
    }
});

//creates a full screen tappable image
Ext.create('MyApp.view.Image', {
    title: 'Orion Nebula',
    description: 'The Orion Nebula is rather pretty',

    src: 'http://apod.nasa.gov/apod/image/1202/oriondeep_andreo_960.jpg',
    fullscreen: true
});



이 소스를 보시면 우리의 클래스에 두개의 configuration들을 달았습니다. 이 두개는 null로 정의했습니다. 이 새 클래스의 인스턴스를 생성할 때 title과 description config를 pass 할 겁니다.

초가화와 onTap 함수가 일어날 때 어떤 동작들을 하게 됩니다. initialize 함수는 컴포넌트가 instant될 때 불려 집니다. 그러니까 이 위치는 이벤트 리스너와 같은 behavior를 셋업하기 아주 좋은 장소 입니다. 첫번째로 우리가 해야 할 일은 이 initialize 함수를 사용하는 것 입니다. (역자주:자바의 생성자가 연상되네요.) callParent(인수) 는 initialize 함수를 부른 superclass 입니다. 이 부분은 아주 중요합니다. 이것을 빼먹으면 여러분의 component는 정확하게 동작하지 않을 겁니다.

callParent를 한 다음에 tap 리스너를 달았습니다. 이 리스너를 단 객체를 tap 하게 되면 onTap 함수가 call 될 겁니다. Sencha Touch 2이 모든 컴포넌트들은 이런 식으로 DOM 객체나 styling을 추가하거나 제거할 때 또는 Ext.dom.Element에서 하는 일반적인 작업들에 대한 이벤트를 listen하기위해 사용할 수 있는 한개의 element property 가 있습니다.

onTap 함수는 아주 간단합니다. 이 함수를 부시면 이 이미지에 대한 정보를 pop up으로 보여주기 위해 Ext.Msg.alert을 사용했습니다. 여기서 title과 description 두개의 config들을 봅시다. 둘 다 getter 함수를 receive 받습니다. (getTitle, getDescription) 이렇게 되면 setter 함수(setTitle, setDescription)도 generate 되게 됩니다.


Advanced Configurations

클래스에 새 configuration option을 생성할 때 getter와 setter 함수들이 생성됩니다. 그래서 아래 코드에서 border는 자동적으로 getBorder와 setBorder 함수를 받게 됩니다.

Ext.define('MyApp.view.MyView', {
    extend: 'Ext.Panel',

    config: {
        border: 10
    }
});

var view = Ext.create('MyApp.view.MyView');

alert(view.getBorder()); //alerts 10

view.setBorder(15);
alert(view.getBorder()); //now alerts 15

getter와 setter만 생성되는 것은 아닙니다. applyBorder와 updateBorder도 같이 생성이 됩니다.

Ext.define('MyApp.view.MyView', {
    extend: 'Ext.Panel',

    config: {
        border: 0
    },

    applyBorder: function(value) {
        return value + "px solid red";
    },

    updateBorder: function(newValue, oldValue) {
        this.element.setStyle('border', newValue);
    }
});

우리의 applyBorder함수는 border configuration이 세팅되거나 바뀔 때 내부적으로 불려지게 됩니다. 여기에 유저가 (혹은 앱 실행중에) 값을 바꿀 때 어떤 변화를 주는 동작을 넣기에 아주 좋습니다. 이 예제에서는 border 의 width를 받아서 CSS border specification string에 전달을 해 줄 겁니다.

예를 들어 border 가 10dl ehlaus applyBorder 함수는 10픽셀의 빨간 선으로 만들어 줄 겁니다. 이 apply 함수는 선택사항입니다. 여기에는 반드시 return 값이 있어야 됩니다. 그렇지 않으면 아무 동작도 일어나지 않습니다.

updateBorder 함수는 applyBorder 함수가 값을 변경하고 나서 불려집니다. 이것은 대개 DOM을 수정하고 AJAX request를 send 할 때 혹은 다른 프로세스 종류를 수행하기 위해 사용됩니다. 이 예제에서는 단지 view의 element를 get 해서 setStyle을 이용해서 border style을 업데이트 할 겁니다. 그러면 setBorder가 call 될 때마다 우리의 DOM은 새로운 스타일을 반영하기 위해 즉시 업데이트 될겁니다.

아래 예제가 있습니다.
화면 위에 border의 width를 변경할 수 있도록 +,- spinner button을 달았습니다. Spinnerspin 이벤트를 받아서 우리가 만든 view의 새로운 setBorder 함수를 call 할 겁니다.

//as before
Ext.define('MyApp.view.MyView', {
    extend: 'Ext.Panel',

    config: {
        border: 0
    },

    applyBorder: function(value) {
        return value + "px solid red";
    },

    updateBorder: function(newValue, oldValue) {
        this.element.setStyle('border', newValue);
    }
});

//create an instance of MyView with a spinner field that updates the border config
var view = Ext.create('MyApp.view.MyView', {
    border: 5,
    fullscreen: true,
    styleHtmlContent: true,
    html: 'Tap the spinner to change the border config option',
    items: {
        xtype: 'spinnerfield',
        label: 'Border size',
        docked: 'top',
        value: 5,
        minValue: 0,
        maxValue: 100,
        incrementValue: 1,
        listeners: {
            spin: function(spinner, value) {
                view.setBorder(value);
            }
        }
    }
});



Usage in MVC

애플리케이션을 만들 때 MVC 모델을 따를 것을 권장합니다. 그러면 이 애플리케이션의 코드는 잘 구성되고 재사용하기 쉽도록 만들어 지게 될 겁니다. 그리고 위에서 사용했듯이 naming을 잘 사용하면 아주 심플하게 사용하기 쉽습니다.

위의 MyApp.view.MyView 클래스는 app/view/MyView.js 에 작성 되어야 합니다. 이래야지 어플리케이션이 자동적으로 이 클래스를 찾아서 로드를 할 수 있습니다. MVC 기반의 Sencha Touch 앱의 파일 구조에 익숙하지 않으시더라고 간단히 배울 수 있습니다. Sencha Touch app은 단지 하나의 html 파일과 하나의 app.js 파일이 있고 model과 view 그리고 controller들이 있게 됩니다. 이 세 요소들은 app/model, app/view 그리고 app/controller 디렉토리에 있게 됩니다.

index.html
app.js
app/
    controller/
    model/
    view/
        MyView.js
       
여러분은 여러분이 만들고 싶은 만큼의 view를 만들 수 있고 이 파일을 app/view 디렉토리에 넣으시면 됩니다. 그리고 이 것을 app.js안에 specifying 하시면 이것들은 자동적으로 로드 될 겁니다.

//contents of app.js
Ext.application({
    name: 'MyApp',
    views: ['MyView'],

    launch: function() {
        Ext.create('MyApp.view.MyView');
    }
});

이 간단한 view naming convention을 따름으로서 우리는 애플리케이션 안에 우리가 만든 뷰 클래스들의 인스턴스를 쉽게 로드하고 생성할 수 있게 됩니다. 위에 있는 예제들이 이 convention을 따랐습니다. 어플리케이션의 launch 함수에서 MyView 클래스를 로드하고 인스턴스를 생성했습니다. Sencha Touch 의 MVC 앱에 대해 좀 더 많은 것을 알고 싶으시면 intro to apps guide를 보세요.

반응형

Sencha Touch 2 Tutorial - View - 01

2012. 3. 8. 21:34 | Posted by 솔웅


반응형
이번주에 사무실이 이사갑니다.
짐을 다 싸 놔서 인터넷도 못하네요.

이제 봄도 되고 사무실도 새로운 곳으로 옮겨지니 새 기분으로 열심히 일 하자고 다짐해 보게 됩니다.

오늘은 Sencha Touch 2.0의 View에 대해서 공부하겠습니다.
오늘 그 내용을 다 다루지는 못하구요. 두번에 나눠서 다루겠습니다.

======= o ======= o ======= o ======= o ======= o

Using Views in your Applications

유저 입장에서 보면 어플리케이션은 단지 view의 모임들일 뿐입니다. 앱에 Model과 Controller가 아주 중요한 역할을 하지만 View가 바로 유저에게 직접 보여지는 부분입니다. 오늘은 이런 뷰를 어떻게 만드는지에 관해 공부해 보겠습니다.

Using Existing Components

View를 생성하는 가장 쉬운 방법은 Ext.create를 사용하는 겁니다. 예를 들어 HTML과 Panel을 만들기를 원한다면 아래처럼 하면 됩니다.

Ext.create('Ext.Panel', {
    html: 'Welcome to my app',
    fullscreen: true
});



예제를 보시면 HTML이 들어간 이 Panel은 전체 화면을 차지하도록 돼 있습니다. Panel이외에도 Sencha Touch에서 제공하는 모든 컴포넌트들을 이런식으로 사용하시면 됩니다. 하지만 바람직한 방법은 특별히 구현하고자하는 것을 subclass로 만들고 나서 생성하는 방법입니다. 아래 예제를 보시죠.

Ext.define('MyApp.view.Welcome', {
    extend: 'Ext.Panel',

    config: {
        html: 'Welcome to my app',
        fullscreen: true
    }
});

Ext.create('MyApp.view.Welcome');

phone에 나오는 화면은 이전과 똑 같습니다. 하지만 다른 점은 새로운 컴포넌트를 가지게 됐습니다. 이 컴포넌트 안에서 여러 작업을 할 수 있습니다. 이것이 일반적으로 앱을 만들때 사용하는 패턴입니다. component가 있는 subclass를 만들고 instance는 나중에 만드는 겁니다. 그럼 어떤게 바뀌었는지 살펴 볼까요?

- Ext.define은 새로운 class를 만들 때 사용합니다. 그리고 그 안에서 extend를 이용해서 Ext.panel 같은 컴포넌트들을 불러와서 사용할 수 있습니다. (Componen에는 4가지가 있는데 Navigation, Store-bound, Form, General Components 가 있습니다. 자세한 내용은 여기 를 참고하세요.)
- MyApp.view.MyView 형식으로 새 뷰 클래스를 만들었습니다. 이 형식은 개발자 마음대로 사용하셔도 되지만 이 형식을 유지할 것을 권장합니다.
- 이 새 클래스에서 우리는 config 를 정의했습니다.
이 config는 subclass의 config block에서 정의하셔도 되고 create() 로 instance를 만들 때 정의하셔도 됩니다.

아래 예제에서는 subclass가 아니라 create() 안에서 object를 pass하는 것을 보여 줍니다.
결과 화면은 CSS가 적용되서 보기 좋은 폰트가 display 됩니다.




A Real World Example

아래 예제는 Sencha Touch 의 Twitter 앱에서 실제 사용한 view class 중 한 부분 입니다.
Ext.define('Twitter.view.SearchBar', {
    extend: 'Ext.Toolbar',
    xtype : 'searchbar',
    requires: ['Ext.field.Search'],

    config: {
        ui: 'searchbar',
        layout: 'vbox',
        cls: 'big',

        items: [
            {
                xtype: 'title',
                title: 'Twitter Search'
            },
            {
                xtype: 'searchfield',
                placeHolder: 'Search...'
            }
        ]
    }
});

이 예제는 바로 전 예제와 같은 패턴을 따르고 있습니다. 새로운 클래스로 Twitter.view.SearchBar를 생성했습니다. 이 클래스는 framwork의 Ext.Toolbar 클래스를 extends 했습니다. 그리고 몇가지 configuration 옵션이 있습니다. layout하고 items array 입니다.

몇개 새롭게 나온 옵션들에 대해서 알아보죠.

: requires - items array를 위해 searchbar를 사용하기 때문에 이 새 뷰에 Ext.field.Search 클래스를 사용할거라고 알려줘야 합니다.
: xtype - 새 클래스 만의 xtype을 줍니다. 간편하게 configuration object를 생성하도록 합니다. 이렇게 함으로서 우리는 여러 방법으로 새로운 뷰 클래스의 인스턴스를 생성할 수 있습니다. 아래와 같이 이 xtype을 이용하시면 편리하게 방금 만든 새로운 클래스를 쉽게 재 사용하실 수 있습니다.

//creates a standalone instance
Ext.create('Twitter.view.SearchBar');

//alternatively, use xtype to create our new class inside a Panel
Ext.create('Ext.Panel', {
    html: 'Welcome to my app',

    items: [
        {
            xtype: 'searchbar',
            docked: 'top'
        }
    ]
});

======= o ======= o ======= o ======= o ======= o

다음 시간에 나머지 View 부분 마저 다루겠습니다.

오늘도 추천 한방씩 ~~~~~ ~~~~~ 부탁드려여...
반응형


반응형
Corona SDK에서 명예 홍보 대사 임명됐다는 메일을 담당자로부터 그냥 개인적으로 들었는데 오늘 공식적으로 임명 됐다는 메일이 왔어요. :)
원래는 한달 쯤 전에 공식 발표 될 거라고 했는데... 이것 저것 준비 하다가 늦어졌다고 하네요.

아래가 메일 중 일 부분이예요.

======== o ========= o ========= o ========= o ========= o

Dear Corona Ambassador,
코로나 대사 귀하

Welcome to the Corona Ambassador Program! You are part of our very first batch of 42 Corona Ambassadors! We have members from all over the world, from San Jose to Sydney.

코로나 대사 프로그램에 오신것을 환영합니다. 당신은 첫번째 Corona 대사로 임명된 42명중 한명입니다. 코로나 대사는 산호세부터 시드니까지 전 세계에 걸쳐 있습니다.

Thank you for joining and for bearing with us while we rolled this out. It has taken us longer than we'd like, but we've been very busy. As you know, we are seeing more and more activity around Corona every single day and there are many things to do.

이렇게 참여해 주시고 우리가 이 일을 다 처리할 때까지 기다려 주셔서 감사합니다. 당초 예상보다 늦어졌습니다. 아주 바빴거든요. 아시다시피 매일매일 코로나 관련 여러 활동들이 있었고 많은 처리 해야 할 일들이 있었습니다.

In any case, we are excited to be officially starting the program. As I mentioned to you when we spoke, our goals are to spread the word about Corona SDK, develop closer relationships with you, our top developers, and to help you achieve your goals - from just meeting other Corona developers to discussing app ideas to growing your businesses.

어쨌든 이렇게 공식적으로 코로나 대사 프로그램을 시작할 수 있어서 매우 기쁩니다. 당초 말씀 드렸듯이 우리의 목표는 Corona SDK를 전 세계에 홍보하는 것이고 개발자들과 대사간의 긴밀한 관계를 맺도록 하는 것입니다. 이러한 목표를 달성하기 위해서 우리 코로나의 top 개발자들이 당신을 도울 겁니다. 단순한 코로나 개발자들의 모임에서부터 코로나를 사용하는 분들의 비지니스 성장을 위한 앱 아이디어관련 토론 등 여러 분야에서 도움을 드릴겁니다.

------------------

In exchange, we'd like to work with you to organize events/meetups in your area every 30 to 60 days - we'll help promote these events and cover costs. 
Also, feel free to send me any comments/ideas/questions you may have. We are open to anything that will help grow this program and spread the word about Corona.

이를 위해 여러분도 30일에서 60일에 한번은 코로나 관련한 이벤트나 모임을 만드시기를 부탁드립니다. 이 이벤트와 비용에 대해 일정 부분 도움을 드리겠습니다.
그리고 어떤 코멘트,아이디어, 질문 이라도 좋으니 많은 의견 보내 주세요. 이 프로그램이 좀 더 활성화 될 수 있고 Corona가 전 세계에 좀 더 많이 퍼질 수 있다면 어떤 의견이라도 받아들이겠습니다.

--------------------

Thanks again and I'm looking forward to working with all of you!
다시 한번 감사드리며 여러분과 같이 일하게 돼 기쁘다는 말씀 전해 드립니다.

David

======== o ========= o ========= o ========= o ========= o

이렇게 공식적으로 대사 임명 메일을 받으니 아주 기분이 좋네요.

코로나에서는 대사가 진행하는 모임의 의견이나 질문을 듣는 공식적인 루트를 마련해 준다고 했습니다.

그러니 여러분이 개발하시면서 아니면 다른 코로나 관련한 질문이 있으시면 제가 정리해서 질문을 할 수 도 있습니다. 그러면 좀 더 성의있고 빠른 답변을 받을 수 있을 것으로 기대합니다.

제대로 코로나 SDK 개발자 모임을 만들려고 합니다.

혹시 Tistory에서 특정 메뉴를 membership으로 운영하는 방법이 있는지 모르겠습니다.
아니면 카페를 만들어야 하는지.. 그리고 제가 미국에 있어서 Online 모임을 할 수 있는 방법이 무엇이 있는지 등등에 대해 여러분의 조언을 바랍니다.

membership이 완성이 되면 따로 online모임을 할 수도 있고 서울이나 지방 아니면 여기 뉴저지나 뉴욕에 사는 멤버들끼리 모임을 유도할 수도 있을 것 같구요.

물론 그 모임에 어떤 지원을 할 수 있을지 제가 코로나 측에 알아봐서 지원을 해 드릴 수도 있습니다.

혹시 member가 되시고 싶은 분 여기 댓글에 남겨 주세요. 그리고 모임을 구성할 정도가 되면 어떻게 모임을 이끌어 나가면 좋을지에 대한 의견 있으시면 올려주시면 아주 도움이 많이 되겠습니다.

저도 많이 고민해 보겠습니다.

같이 코로나 SDK 개발의 전문가가 되고 싶으신 분 여기여기 모이세요. ~~~~~~~



~~~~~~~~~~
반응형

Sencha Touch 2 Tutorial - Controllers -

2012. 3. 6. 12:19 | Posted by 솔웅


반응형
Controllers

Controller는 어떤 event가 일어 났을 때 다른 어떤 동작이 일어날 수 있도록 Control 하는 역할을 합니다. 만약 앱에 로그아웃 버튼이 있다면 유저는 이 버튼을 tap 할테고 Controller는 이 버튼에 대한 tap event를 listening 하고 있다가 이벤트가 발생하면 로그아웃 시키는 동작을 하도록 합니다. 이러한 기능은 View 클래스가 보여 주는 데이터를 변경하도록 할 수 있고 또 Model 클래스가 데이터를 로딩,저장 등의 동작을 할 수 있도록 해 줍니다. Controller는 이 사이에서 그런 동작들이 자연스럽게 이뤄질 수 있도록 Control 해 줍니다.

Relation to Ext.app.Application

Controller는 어플리케이션의 context 안에 존재합니다. 하나의 어플리케이션은 대개 어떤 특정한 부분을 handle 하는 여러 Controller들로 구성 돼 있습니다. 예를 들어 온라이 쇼핑 싸이트에서 주문을 하는 앱을 생각해 보면 이 앱에는 주문과 고객 그리고 상품들에 대한 Controller들이 있을 겁니다.

모든 컨트롤러는 그 어플리케이션의 Ext.app.Application.controller config에서 정해 주시면 됩니다. 이 어플리케이션은 각 컨트롤러를 자동적으로 instantiate 시켜주고 계속 참조하게 됩니다. 그래서 컨트롤러를 직접 instantiate 시키는 상황은 특별한 경우에만 한합니다. convention에 의해 컨트롤러들은 명명되는데 대개 Model 작업 수행 이전에 복수개의 컨트롤러들에 대해 이 작업이 이뤄집니다. 예를 들어 MyApp이라는 앱이 있고 여기에 컨트롤러가 Product를 관리한다면 convention은 app/controller/Products.js라는 파일에 MyApp.controller.Products 라는 클래스를 생성합니다.

Launching

어플리케이션이 launch 될 때 4가지의 주요 단계를 거치게 됩니다. 2가지는 컨트롤러 안에서 행해집니다. 첫번째로 각각의 컨트롤러는 init 함수를 정의할 수 있게 됩니다. 이것은 Application launch 함수 이전에 call 됩니다. 두번째는 Application과 Profile launch 함수가 call 된 이후인데요, 프로세스의 마지막 단계로 컨트롤러의 launch 함수가 call 됩니다.

    Controller#init functions called
    Profile#launch function called
    Application#launch function called
    Controller#launch functions called

대개 Controller-specific launch 로직은 Controller의 launch 함수 안에 있어야 합니다. 왜냐하면 이것은 Application과 Profile launch 함수 이후에 call 되기 때문입니다. 바로 이 시점에 initial UI가 있게 됩니다. 만약 app launch 이전에 Controller-specific processing이 필요하다면 Controller init 함수를 implement 할 수 있습니다.

Refs and Control

컨트롤러에서 중요한 두가지는 refscontrol configuration입니다. 이 둘은 앱의 Component들에 쉽게 reference들을 얻을 수 있게 해주고 어떤 이벤트가 발생하면 이에 대해 어떤 동작이 일어날 수 있도록 해 줍니다. refs 먼저 보겠습니다.

Refs

Refs는 아주 강력한 ComponentQuery 신택스에 영향을 주는것으로 각 페이지에 쉽게 Comopnent들을 위치시킬 수 있도록 합니다. 각 콘트롤러에 대해 원하는 만큼의 refs를 정의할 수 있습니다. 예를 들어 아래 예제에서는 maniNav라는 아이디라는 Component를 찾는 nav라 불리는 ref를 정의합니다. 그 다음에 그 아래에 addLogoutButton 안의 ref를 사용합니다.

Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            nav: '#mainNav'
        }
    },

    addLogoutButton: function() {
        this.getNav().add({
            text: 'Logout'
        });
    }
});

대개 ref는 key/value 조합입니다. 키(위의 경우 nav)는 reference의 이름으로 ref를 생성하기 위해 사용됩니다. 그리고 값(위의 경우 #mainNav)는 ComponentQuery selector로 Componet를 찾을 때 사용 됩니다.

그 아래에 addLogoutButton이라는 간단한 함수를 call 했습니다. 이것은 getNav 함수를 발생시키면서  이 ref를 사용하게 될 겁니다. 이런 getter 함수들은 당신이 정의한 refs를 바탕으로 발생됩니다. 이 함수의 이름은 get 다음에 ref의 대문자(NAV)를 붙여서 이름을 만드는 규칙을 따릅니다. 이 경우엔 nav reference가 툴바이고 함수가 호출 돼었을 때 이 툴바에 Logout 버튼을 추가하도록 했습니다. 이 ref는 아래와 같이 툴바를 인식할 겁니다.

Ext.create('Ext.Toolbar', {
    id: 'mainNav',

    items: [
        {
            text: 'Some Button'
        }
    ]
});

우리가 addLogoutButton 함수를 run 할 때 이미 툴바가 생성돼 있었다고 가정합시다. (이 과정이 어떻게 진행되는지는 나중에 볼 겁니다.) 이 경우에는 두번째 버튼이 생길 겁니다.

Advanced Refs

Refs는 name과 selector 이후에 몇개의 추가 옵션을 설정할 수 있습니다. autoCreate, xtype 등이 그것인데 대부분 같이 사용 됩니다.

Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            nav: '#mainNav',

            infoPanel: {
                selector: 'tabpanel panel[name=fish] infopanel',
                xtype: 'infopanel',
                autoCreate: true
            }
        }
    }
});

이제 우리의 Controller에 두번째 ref를 add 했습니다. 마찬가지로 이름이 key 입니다. 이경우에는 infoPanel이 되겠죠. 여기서는 value(값) 대신 object를 pass 하고 있습니다. 조금 더 복잡한 selector query도 있죠? 한번 상상해 보세요. 앱이 tab panel을 가지고 있고 그 tab 판넬의 아이템 중 하나가 이름이 fish라구요. 위 예제의 selector는 tab panel 아이템 안에 infopanel이라는 xtype을 가진 Component를 match 할 겁니다.

이 예제에서 좀 다른 점은 infopanel이 fish panel 안에 존재하지 않는다면 Controller안에서 this.getInforPanel을 call 할 때 자동적으로 생성하게 될 겁니다. 그 이유는 이 컨트롤러의 selector가 아무것도 return 하지 않고 event 시 instantiate 하도록 xtype을 제공하기 때문입니다.

Control

refs config와 짝을 이루는 것이 바로 control입니다. 콘트롤은 콤포넌트에 의해 일어나는 이벤트나 Controller의 어떠한 react를 listening 하는 수단입니다. Control은 자신의 key로 Component ComponentQuery selectors 와 refs를 받습니다. 아래 예제가 있습니다.

Ext.define('MyApp.controller.Main', {
    extend: 'Ext.app.Controller',

    config: {
        control: {
            loginButton: {
                tap: 'doLogin'
            },
            'button[action=logout]': {
                tap: 'doLogout'
            }
        },

        refs: {
            loginButton: 'button[action=login]'
        }
    },

    doLogin: function() {
        // called whenever the Login button is tapped
    },

    doLogout: function() {
        // called whenever any Button with action=logout is tapped
    }
});

이 예제에서 우리는 두개의 control을 정의했습니다. 하나는 loginButton ref를 위한 것이고 다른 하나는 logout 기능을 할 한 버튼을 위한 겁니다. 이 각각의 정의에 하나의 이벤트 핸들러를 넣었습니다. 바로 tap 이벤트에 대해 listening 하도록 했습니다. 이 버튼들에 tap 이벤트가 발생하면 어떤 동작이 일어날 겁니다. tap 다음에 doLogin과 doLogout이 있죠? 이건 함수 이름입니다. 어딘가에 이 함수가 있고 그 안에 필요한 기능들이 코딩 돼 있을 겁니다. 중요한 부분이죠.

각각의 control 정의할 때 원하는 만큼의 이벤트 리스너를 달 수 있습니다. 그리고 키로서 key로서 ComponentQuery selector와 refs를 섞어서 매치할 수 도 있습니다.

Routes

Sencha Touch 2에서 Controller는 route를 direct하게 명시할 수 있습니다. 이것은 앱 안에서 history support 제공을 가능하게 하며 앱 내에 어떤 페이지든지 직접 링크를 걸어서 가도록 할 수 있습니다. 이것이 route를 제공하기 때문에 가능한 일들 입니다.

예를 들어 로그인과 유저 프로파일을 보여주는 일을 담당하는 Controller가 있다고 합시다. 그리고 이 화면이 url로 접근 할 수 있도록 하고 싶다고 합시다. 아래와 같이 하시면 될 겁니다.

Ext.define('MyApp.controller.Users', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'login': 'showLogin',
            'user/:id': 'showUserById'
        },

        refs: {
            main: '#mainTabPanel'
        }
    },

    // uses our 'main' ref above to add a loginpanel to our main TabPanel (note that
    // 'loginpanel' is a custom xtype created for this application)
    showLogin: function() {
        this.getMain().add({
            xtype: 'loginpanel'
        });
    },

    // Loads the User then adds a 'userprofile' view to the main TabPanel
    showUserById: function(id) {
        MyApp.model.User.load(id, {
            scope: this,
            success: function(user) {
                this.getMain().add({
                    xtype: 'userprofile',
                    user: user
                });
            }
        });
    }
});

위에서 명시한 routes는 간단하게 브라우저 address bar를 Controller 함수로 매핑 시킵니다. 이 routes는 login route 같이 http://myapp.com/#login에 매치 되는 간단한 텍스트가 될 수도 있습니다. 또는 http://myapp.com/#user/123 같이 url에 매치되는 user/:id route 같이 wildcard를 가지고 있을 수도 있습니다. 주소가 바뀔 때마사 콘트롤러는 명시된 함수를 자동적으로 call 합니다.

showUserById 함수에서 유저 인스턴스를 첫번째로 로드하는 부분을 주의해서 보세요. route를 사용할 때마다 이 함수는 그것과 관련한 데이터나 상태 저장에 대해 완료하는 책임을 갖는 route에 의해 불려지게 됩니다. 유저가 이 url을 다른 사람에게 전달할 수도 있고 아니면 단순하게 그 페이지를 refresh 할 수도 있기 때문입니다. 그래서 지금 로드 했던 것들을 cache에서 지워버릴 필요가 있기 때문입니다. route와 관련해서 restoring state에 대한 좀 더 자세한 설명은 application architecture guides에서 보실 수 있습니다.

Before Filters

마지막으로 Routing의 context내에서 Controller가 제공하는 것은  routes를 작성하기 이전에 작성되는 filter 함수 정의 before 입니다.

Ext.define('MyApp.controller.Products', {
    config: {
        before: {
            editProduct: 'authenticate'
        },

        routes: {
            'product/edit/:id': 'editProduct'
        }
    },

    // this is not directly because our before filter is called first
    editProduct: function() {
        //... performs the product editing logic
    },

    // this is run before editProduct
    authenticate: function(action) {
        MyApp.authenticate({
            success: function() {
                action.resume();
            },
            failure: function() {
                Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
            }
        });
    }
});

유저가 http://myapp.com/#product/edit/123같이 url로 navigate할 때마다 컨트롤러의 authenticate 함수가 불려질겁니다. 그리고 만약 before 필터가 존재하지 않을 경우 Ext.app.Action을 pass할 겁니다. Action은 단순하게 Controller, function(이 예제의 경우 editProduct)를 표현합니다.  rmflrh url에 있는 ID 같은 다른 데이터도 표현합니다.

이제 필터는 동기적으로나 비동기적으로 필요한 일을 할 수 있습니다. 이 예제의 경우에는 어플리케이션의 유저가 제대로 로그인 정보를 주었는지에 대한 authenticate 함수를 실행합니다. 이 동작은 서버에 있는 정보와의 비교가 필요하기 때문에 AJAX request를 사용할 것이고 비동기적으로 작동될 겁니다. 인증이 성공하면 action.resume() 함수를 call 해서 다음 동작을 이어 가게 됩니다. 만약 인증되지 않으면 다시 로그인 하도록 해야 합니다.

before 필터는 어떤 특정 action이 행해지기 이전에 추가적인 클래스들을 load하기 위해 사용 될 수 있습니다. 예를 들어 어떤 동작은 드물게 실행 되어서 필요한 상황이 될 때까지 로딩을 하지 않고 싶은 경우가 있을 수 있습니다. 그러면 애플리케이션이 시작할 때 좀 더 빠르게 시작되게 할 수 있겠죠. 이를 위해 간단히 필요할 때 로드될 수 있도록 Ext.Loader를 사용하면 됩니다.

각 action에 대해 원하는 만큼의 before 필터가 명시 될 수 있습니다. 1개 이상의 필터를 사용하려면 배열을 pass 해 주면 됩니다.

Ext.define('MyApp.controller.Products', {
    config: {
        before: {
            editProduct: ['authenticate', 'ensureLoaded']
        },

        routes: {
            'product/edit/:id': 'editProduct'
        }
    },

    // this is not directly because our before filter is called first
    editProduct: function() {
        //... performs the product editing logic
    },

    // this is the first filter that is called
    authenticate: function(action) {
        MyApp.authenticate({
            success: function() {
                action.resume();
            },
            failure: function() {
                Ext.Msg.alert('Not Logged In', "You can't do that, you're not logged in");
            }
        });
    },

    // this is the second filter that is called
    ensureLoaded: function(action) {
        Ext.require(['MyApp.custom.Class', 'MyApp.another.Class'], function() {
            action.resume();
        });
    }
});

이 필터들은 순서대로 call 됩니다. 그리고 다음 단계로 가기 위해서 반드시 각각 action.resume()을 call 해야 합니다.


Profile-specific Controllers

Superclass, shared stuff:

Ext.define('MyApp.controller.Users', {
    extend: 'Ext.app.Controller',

    config: {
        routes: {
            'login': 'showLogin'
        },

        refs: {
            loginPanel: {
                selector: 'loginpanel',
                xtype: 'loginpanel',
                autoCreate: true
            }
        },

        control: {
            'logoutbutton': {
                tap: 'logout'
            }
        }
    },

    logout: function() {
        // code to close the user's session
    }
});

Phone Controller:

Ext.define('MyApp.controller.phone.Users', {
    extend: 'MypApp.controller.Users',

    config: {
        refs: {
            nav: '#mainNav'
        }
    },

    showLogin: function() {
        this.getNav().setActiveItem(this.getLoginPanel());
    }
});

Tablet Controller:

Ext.define('MyApp.controller.tablet.Users', {
    extend: 'MyApp.controller.Users',

    showLogin: function() {
        this.getLoginPanel().show();
    }
});

===== o ===== o ===== o ===== o ===== o =====

오늘도 개념적인 부분을 많이 다뤘습니다. 다음시간에는 View에 대해서 다룰 겁니다.
제목에서 알 수 있듯이 다음 시간에는 코딩하고 결과를 눈으로 확인하면서 할 수 있을 겁니다.
어쨌든 오늘 다룬 Controller도 아주 중요한 부분이니까 어느정도 이해 될 때까지 읽어 보고 다음으로 넘어가야겠습니다.


반응형


반응형
오늘 다룰 주제는 Sencha Touch 2 어플리케이션에 대한 기본적인 구조와 개념들을 다룰겁니다.
이론적인 부분이라서 약간 지루할 수는 있을텐데요. 이런 기본 개념들을 잘 이해하고 넘어가면 실제 코딩할 때 많은 도움이 됩니다.

오늘 글은 Sencha Touch Beta 2에서 제공하는 Tutorial 중 All about Application을 정리 했습니다.

====== o ====== o ====== o ======= o ======= o ======

Intro to Applications with Sencha Touch 2

Sencha Touch 2 는 다양한 플랫폼에서 동작하는 어플리케이션을 만들 수 있도록 제작됐습니다.
Sencha Touch 2는 어플리케이션을 최대한 간단하게 만들도록 하기 위해 간단하면서도 강력한 어플리케이션 아키텍쳐를 제공합니다.
이 아키텍쳐는 MVC(Model View Controller) 패턴에 맞게 만들어 졌습니다.

이 패턴은 가독성 있고 테스트하기 쉽고 유지보수 하기 쉬운 코딩을 지원합니다.
이 외에 다음과 같은 기능들을 제공합니다.

- History Support : 앱 내에서 full back button을 지원하고 어느 위치에서든지 link가 걸릴수 있도록 합니다.
- Deep Linking : 앱의 어떤 화면에서도 deep link들을 공유할 수 있습니다. 그냥 웹 페이지에 링크 걸듯이 하면 됩니다.
- Device Profiles : 어플리케이션의 UI를 phone, 태블릿 기타 디바이스의 종류에 맞게 쉽게 customizing할 수 있도록 지원합니다.

Anatomy of an Application

어플리케이션은 Model,View,Controller,Store,Profile과 아이콘, launch screen 이미지 등 추가적인 metadata들의 조합입니다.




- Models : 앱에서 객체의 타입을 표시합니다. 예를 들어 e-커머스 앱의 경우 유저, 제품, 주문과 관련한 모델이 필요합니다.
- View : 객체나 데이터들을 display하는 것을 다루는 부분입니다.
- Controllers : 어플리케이션 내의 상호 작용을 다룹니다. 어떤 이벤트를 listening하고 핸들링 하는 것도 콘트롤러에서 하게 됩니다.
- Store : 앱에 데이터를 로딩하는 일을 책임 집니다. 리스트나 데이타뷰 같은 컴포넌트들도 다룹니다.
- Profile : 앱의 UI를 디바이스 종류별로 쉽게 customizing 할 수 있도록 도와 줍니다.

어플리케이션은 우선 Sencha Touch 어플리케이션이란 것을 선언하면서 시작합니다.
코드는 대개 아래와 같습니다.

Ext.application({
    name: 'MyApp',
    models: ['User', 'Product', 'nested.Order'],
    views: ['OrderList', 'OrderDetail', 'Main'],
    controllers: ['Orders'],

    launch: function() {
        Ext.create('MyApp.view.Main');
    }
});

name은 전체 어플리케이션을 대표하는 글로벌 namespace입니다. 여기에는 model,view,controller 그리고 다른 클래스들이 다 포함돼 있습니다.
예를 들어 name이 MyApp이라면 이에 따른 클래스들인 MyApp.model.User, MyApp.controller.Users, MyApp.view.Main 등이 포함 돼 있을 겁니다.
애플리케이션은 models, views, controllers 라는 사전 정의 된 configuration을 자동적으로 로딩해서 사용합니다.
이것을 기초로 효율적인 file구조를 만들 수 있습니다. 예를 들어 app/model 폴더나 app/controller 폴더를 만들 수 있고 그 안에 관련된 파일들을 넣을 수 있습니다.
custom dependency에 대해서는  Dependencies section of the Ext.app.Application docs 를 참조하세요.

Controllers

콘트롤러는 어플리케이션을 서로 연결시켜 주는 역할을 합니다. 콘트롤러는 이벤트를 리스닝(예를 들어 button을  tap 하는 이벤트 등)하고 어떤 이벤트가 발생하면 이에 대한 핸들링을 합니다.
이렇게 함으로서 코드를 보기 좋게 작성할 수 있고 view 로직과 control 로직을 구분해서 코딩 할 수 있습니다.
예를 들어 유저가 로그인 폼을 통해 로그인하려고 하는 경우. View에서는 필드나 버튼 같은 form을 작성할 겁니다.
controller는 겉으로 드러나는 부분은 미미하지만 그 역할을 아주 중요합니다. 그리고 몇가지 규칙이 있습니다. 여러분 앱에 있는 각각의 콘트롤러는 모두 Ext.app.Controller의 subclass 입니다. 콘트롤러는 MyApp.controller.* namespace에 존재합니다. 예를 들어 세션 콘트롤러가 있다면 그것은 MyApp.controller.Sessions 라고 불려질겁니다. 그리고 app/controller/Sessions.js 라는 파일로 존재할 겁니다.

각각의 콘트롤러들이 Ext.app.Controller의 subclass 임에도 이 콘트롤러들은 Application 이 처음 로드 될 때 한번 초기화 됩니다. 모든 콘트롤러는 각각 한개의 인스턴스만 있습니다. 그리고 이 콘트롤러 인스턴스 세트는 어플리케이션에 의해 내부적으로 manage 됩니다. 위 예제와 같이 어플리케이션의 콘트롤러 config를 사용하면 모든 콘트롤러와 인스턴스들은 자동적으로 로딩되게 됩니다.

A simple example

아래에 세션 컨트롤러와 관련한 예제가 있습니다. 이 예제에는 2개의 콘트롤러 configuration이 있습니다. (refs,control). Refs는 이 앱의 컴포넌트를 쉽게 찾을 수 있도록 도와 줍니다. 아래 예제의 경우는 formpanel 이라는 xtype과 매치되는 모든 콘트롤러를 검색하고 첫번재로 찾은 컴포넌트에 loginForm 프로퍼티를 할당하게 됩니다. 이 프로퍼티는 나중에 doLogin 함수에서 사용할 겁니다.

두번째는 control configuration입니다. refs처럼 이 configuration은 그 안에 버튼이 있는 formpanel을 찾기 위한 ComponentQuery selector를 사용합니다. 예를 들어 이 콘트롤러는 login form의 submit button을 찾을 겁니다. 유저가 이 버튼을 tap하면 컨트롤러의 doLogin 함수가 불려질 겁니다.

Ext.define('MyApp.controller.Sessions', {
    extend: 'Ext.app.Controller',

    config: {
        refs: {
            loginForm: 'formpanel'
        },
        control: {
            'formpanel button': {
                tap: 'doLogin'
            }
        }
    },

    doLogin: function() {
        var form   = this.getLoginForm(),
            values = form.getValues();

        MyApp.authenticate(values);
    }
});

doLogin은 간단할 겁니다. 왜냐하면 위 예제에서 loginForm ref를 정의해서 Controller가 자동적으로 getLoginForm을 작동시키고 match 되는 formpanel을 리턴합니다. 이 form reference를 가지게 되면 유저 네임과 패스워드 값을 가져와서 이를 검증하는 함수에 전달할 겁니다. 이러한 작업이 컨트롤러가 하는 작업입니다. 이벤트가 발생하는지를 listening 하고 어떤 작업을 하게 됩니다. 이 경우에는 아이디,패스워드 검증을 하는것이죠.

컨트롤러에 대해 좀 더 자세히 알고 싶으시면 여기 controllers guide를 참조하세요.

Stores

store는 데이타를 다루는 매우 중요한 부분입니다. 반면에 그 사용법은 간단합니다. Store는 Model 인스턴스의 배열일 뿐입니다. Data-bound 컴포넌트는 DataView리스트 같이 Store의 각 Model 인스턴스에서 한 item을 render 합니다. Model 인스턴스가 추가되거나 삭제되는 store 이벤트가 발생하면 이 data-bound 컴포넌트가 listening 해서 update를 해 주게 됩니다.

Store가 무엇이고 이것을 앱에서 컴포넌트와 같이 어떻게 사용할 것인가에 대해 더 자세히 아시려면 store guide 부분을 참조하세요. 몇가지 꼭 알아 두셔야 할 부분들이 있으니 꼭 보세요.

Device Profiles

Sencha Touch는 성능이나 해상도가 다른 다양한 device들에서 사용할 수 있도록 해 줍니다. 태블릿에 맞게 작업하면 폰에 안 맞거나 그 반대의 경우가 있을 수 있습니다. 그래서 이런 서로 다른 device에는 그에 맞는 각각의 view들을 만들어야 합니다. 하지만 단지 다른 UI를 보여주가 위해 어플리케이션을 여러개 만들 필요는 없습니다. Sencha Touch는 이 경우에 가능한 많은 code를 서로 share 할 수 있도록 해 줍니다.

Device Profile은 서로 다른 device에 여러분의 앱이 제대로 보여지고 동작할 수 있도록 해 주는 간단한 클래스입니다. 처음에는 이 profile 없이 앱을 개발하실 겁니다. 그리고 이 profile은 나중에 추가 되게 됩니다. 그 앱이 특정 디바이스에서만 동작하는 앱이라면 profile은 작업을 할 필요가 없겠죠. 그리고 특정 디바이스일 경우에 추가적인 model,view,controller 등이 로드 되게 할 수 도 있습니다.

이 프로파일을 사용하기 위해서는 이 프로파일(device)들이 무엇인지 Application에 얘기를 해 주고 그 디바이스들을 위해 Ext.app.Profile 서브클래스를 생성하시면 됩니다.:

Ext.application({
    name: 'MyApp',
    profiles: ['Phone', 'Tablet'],

    //as before
});

위의 경우 어플리케이션은 app/profile/Phone.js와 app/profile/Tablet.js를 로드하게 될 겁니다. 아래에 Tablet에 대한 예제가 있습니다.

Ext.define('MyApp.profile.Tablet', {
    extend: 'Ext.app.Profile',

    config: {
        controllers: ['Groups'],
        views: ['GroupAdmin'],
        models: ['MyApp.model.Group']
    },

    isActive: function() {
        return Ext.os.is.Tablet;
    }
});

isActive 함수는 이 앱이 태블릿에서 작동을 할 때 true를 return 할 겁니다. 이 경우 약간 부정확 할 수도 있습니다. 왜냐하면 태블릿과 폰 사이에 무수 히 많은 모양과 size를 가진 device들이 있거든요. 태블릿인가 혹은 phone인가에 대한 확실한 구분이 사실은 없습니다. Sench Touch의 Ext.os.is.Tablet은 iPad에서 작동하면 true를 리턴하고 다른 경우엔 false를 리턴합니다. isActive에 다양한 디바이스를 넣어서 사용하시면 됩니다.

한가지 명심해야 할 것은 오직 하나의 Profile만이 isActive 함수에서 true를 리턴한 다는 것입니다. 1개 이상이 true일 경우 첫번째 매치되는 것만 true를 return 하고 나머지는 무시 될 겁니다. 그리고 이 후에 이 true로 return 된 첫번째 profile에 맞는 부분들이 실행이 될 거구요.

현재의 프로파일에 대해 추가적인 model,view,controller, store들이 있다면 이는 어플리케이션이 자동적으로 로딩을 할 겁니다. (currentProfile 에 관해서 좀 더 알고 싶으신 분은 여기를 클릭하세요.)아래와 같이 해당 파일을 로드하게 될 겁니다.

    views: ['GroupAdmin'] will load app/view/tablet/GroupAdmin.js
    controllers: ['Groups'] will load app/controller/tablet/Groups.js
    models: ['MyApp.model.Group'] will load app/model/Group.js

대부분의 경우 Profile은 특정 디바이스에 추가적인 기능이나 좀 다른 뷰나 store를 필요로 할 때 사용 될 겁니다. 좀 더 자세한 사항은 device profiles guide를 참고하세요.


Launch Process

각 어플리케이션들에는 launch 함수를 정의할 수 있다. 이 launch 함수는 앱이 로드되고 launch될 준비가 되어있을 때 실행 되는 함수 입니다. start up 로직을 넣을 최적의 장소죠. 특히 main view structure를 생성하기에 적합한 장소입니다.

launch 함수와 더불어 앱의 startup 로직을 넣을 좋은 장소가 두군데 더 있습니다. 첫번째로 각 controller에는 init 함수를 정의할 수 있습니다. 이 함수는 어플리케이션 launch 함수가 시작되기 이전에 call 됩니다. 두번째로는 Device Profile을 사용할 경우인데요. 각각의 Profile에는 나름대로의 launch 함수를 정의할 수 있습니다. 이 함수는 application launch 함수 전에 그리고 controller의 init 함수 이후에 실행 됩니다.

주의 할 것은 해당 Profile에 매치하는 Device에서만 이 Profile launch 함수가 실행된다는 겁니다. 예를 들어 Phone과 Tablet 프로파일을 정의했고 이 앱을 tablet에서 실행할 경우 tablet의 launch 함수만 call 됩니다.
앱이 시작할 때 call 되는 함수들의 순서는 아래와 같습니다.

    Controller#init functions called
    Profile#launch function called
    Application#launch function called
    Controller#launch functions called

Profile을 사용할 때 Profile launch 함수 안에 bootup 로직을 넣는 것이 일반적입니다. 왜냐하면 각 프로파일들은 startup때 실행될 필요가 있는 각기 다른 view 세트를 가지고 있기 때문입니다.

Routing and History Support

Sencha Touch 2는 Routing과 History를 support합니다. Kitchen Sink 같은 SDK 예제를 보면 각 화면별로 옮겨다니기 쉽게 history를 사용해서 지원합니다. 특히 안드로이드의 경우 유용합니다.

이 히스토리 기능을 제대로 보여주는 에제는 Kitchen sink 예제입니다. 이 예제에서는 히스토리를 위한 라우팅과 상대 restoration 과 관련한 많은 문서들이 있습니다.
~~~~~~ ~~~~~~
반응형

Corona Display Groups 101

2012. 3. 1. 22:03 | Posted by 솔웅


반응형
이번주 코로나에서 제공하는 튜토리얼은 Display Group에 대한 겁니다.

기본 API는 여기에 있습니다.
group.numChildren, group:insert(), group:remove() 세가지 요소들이 있습니다.
group.nemChildren은 해당 그룹에 있는 객체들의 숫자를 알 수 있는 메소드 입니다.
주로 그룹 내 모든 객체들에 어떤 변경이나 효과를 줄 때 for 문에서 이용하게 됩니다.
group:insert()는 새로운 객체를 해당 그룹에 추가할 때 사용하구요.
기본은 맨 마지막에 삽입 되는데 원하면 삽입되는 위치도 지정해 줄 수 있습니다.
그리고 group:remove()는 그 그룹에서 특정 객체를 제거할 때 사용합니다.

그룹을 처음 만들 때는 display.newGroup()을 사용하구요.
여기를 클릭하시면 이와 관련되서 정리한 문서를 보실 수 있습니다.

오늘 다룰 튜토리얼의 원본은 여기 있습니다.
이 원본을 정리해 두겠습니다.

======= o ======= o ======= o ======= o ======= o ======= o

Corona Display Groups 101



이번주 다룰 주제는 모든 개발자들이 가장 자주 접하는 코로나 프로그래밍에서 아주 기본이 되는 group에 관련 해서 다루겠습니다. 이 group을 자주 사용했던 개발자 분들에게도 아무 도움이 되는 내용일 겁니다.

What are Groups?

코로나에서 화면에 나오는 모든 객체들은 display object 입니다. 이 display object에는 세가지 유형이 있습니다.
- Vectors (Shapes & Text)
- Images (익히 알려져 있는 "normal" display objects)
- Groups

코로나 display group(이후 부터는 group으로 하겠습니다.)은 좌표(x,y), 투명도, 회전값 등의 여러 property들과 메소드들을 가지고 있습니다. 이는 이 object를 control 하는데 아주 편리한 기능을 제공해 줍니다.

Object Grouping

group의 목적은 다른 object 들을 하나로 묶는 것입니다. 그래서 여러개의 object들을 하나의 object 처럼 다룰 수 있도록 해 줍니다. 이 기능은 프로그래밍 할 때 손쉽게 여러 object들을 제어할 수 있게 도와줘서 편리하기도 하지만 반면에 불편한 점도 있습니다.
여러분들이 어떤 display object를 group에 넣고 이 그룹의 property들을 변경하더라도 그 그룹 안에 있는 개별 object들의 property들은 변경되지 않습니다. 다만 group의 property에 따라서 display 될 뿐입니다.
이 group이 또 다른 group에 포합 될 수도 있고 그 group이 또 다른 group에 포함될 수도 있습니다. 이 경우에도 group의 프로퍼티와 그룹 내 object의 프로퍼티의 관계는 위와 같습니다.
이런식으로 group의 포함관계가 많이 될 수록 이 그룹과 그 안의 요소들을 콘트롤 하는게 만만치 않습니다.

하나 예를 들어보죠.

A와 B 라는 그룹이 있습니다. 그리고 보통 display object인 C 가 있습니다. 모두 위치는 0,0 입니다. C가 B에 insert 됐습니다. B는 다시 A에 insert 됐습니다. 그러면 이제 A의 x를 100으로 바꾸고 B의 x를 50으로 바꾸겠습니다. C의 좌표는 여전히 0,0입니다. 하지만 이 C가 실제로 화면에 표시 될 때는 150,0 의 위치에 표시 됩니다. 왜 그럴까요? 왜냐하면 그 parent인 B의 x 값이 50이고 또 B의 parent인 A의 x 값이 100이기 때문입니다.

약간 혼란스럽기는 하지만 실제 프로그래밍할 때는 그렇게 크게 문제가 되지는 않죠? 그냥 group별로 display 되면 되니까요.  하지만 touch 리스너에 따라 그룹 내의 특정 object를 움직이거나 회전시키거나 크기를 변화시킬 때는 약간 혼란 스러울 수가 있습니다. 만약 여러분의 parent group들의 값이 변경 됐을 때 그 children들에 어떻게 영향을 미치는지 자세히 알고 있다면 이런 경우 문제없이 프로그래밍을 하실 수 있을 겁니다.

Group Methods

Group에는 insert와 remove라는 두개의 메소드가 있습니다.

group:insert( [index,], child, [, resetTransform] )

이 메소드는 group에 display object 를 insert 할 때 사용됩니다. 이 함수에는 한개의 필수 parameter가 있고 두개의 옵션 파라미터들이 있습니다.

아래 예제는 이 insert  함수를 어떻게 사용하는지에 대해 보여줍니다. 주석을 잘 보시면 그룹의 프로퍼티들이 바뀔 때 그 안의 child는 어떤 영향을 받는지 잘 설명돼 있습니다.

-- 이미지 객체를 생성한다. 객체의 이름은 object1이고 위치는 50,50이다.
local object1 = display.newImage( "image.png" )
object1.x, object1.y = 50, 50
-- display group을 생성한다. 그룹의 이름은 group1이다.
local group1 = display.newGroup()
-- 그룹의 위치를 100,100으로 한다.
group1.x, group1.y = 100, 100
-- 이미지 객체를 그룹에 insert 한다.
group1:insert( object1 )
-- 이미지 객체의 위치는 50,50이지만 화면에 나타날 때는 150,150 위치에
나타난다.
-- group1에 insert 돼 있기 때문에 group의 위치인 100,100이 더해
지기 때문이다.

디폴트로 insert된 객체는 그 그룹의 top 에 위치 됩니다
디폴트로 insert된 객체를 그 그룹의 top에 위치 됩니다. 하지만 옵션 파라미터인 첫번째 argument인 index 번호를 바꿈으로서 해달 객체의 순서를 지정할 수 있습니다.
이 때 index번호 1은 가장 하위를 가리킵니다. 새로운 객체를 1로 지정하면 이전의 객체들은 하나씩 위로 올라갑니다. 다른 위치에 insert해도 그 상위의 객체들은 하나씩 위로 올라가게 됩니다.

예를 들어서 그룹에 4개의 객체들이 있는데 여러분이 새 객체를 index 위치 2로 지정하면 새 객체를 그룹내의 두번째 객체가 되고 기존의 2,3,4번째 객체들은 3,4,5번째 객체들로 바뀌게 됩니다.

만약에 새 객체를 그 그룹 안에 있는 객체수 보다 더 큰 수의 index 위치로 지정하게 되면 코로나는 그냥 default 로 처리해서 top에 위치시킵니다.

그 외에 optional parameter로 resetTransform이 있습니다. 만약에 여러분이 객체들의 위치, 회전, 투명도, 크기 등을 그룹에 insert 할 때 디폴트값으로 재 세팅하기를 원하신다면 이것은 true로 세팅 돼 있어야 합니다. 즉 그 객체가 가지고 있는 기본 값(위치, 회전, 투명도 등)은 적용되지 않고 오직 group의 값만 적용 되게 됩니다. resetTransform의 디폴트 값은 false입니다.

IMPORTANT NOTE

하나의 객체는 한 그룹 내에만 소속될 수 있습니다. 그렇기 때문에 객체를 group A에 insert 한 후 다시 group B에 insert 하면 이 객체는 A의 그룹에서는 자동으로 해제 되게 됩니다.
참고로 코로나의 display stage도 하나의 display group입니다. 그렇기 때문에 display object나 그룹이 생성되면 자동적으로 이 stage 그룹에 포함 되게 됩니다.
그렇기 때문에 한 객체가 다른 그룹에 insert 되면 그 객체는 더이상 stage에 소속 돼 있지 않겠죠. 하지만 그 그룹의 stage에 소속돼 있으니까 화면에 display하는데는 문제 없을 거예요.

group:remove( indexOrChild )

이 메소드는 그룹 내에 있는 특정 객체를 remove 하기 위해 사용 됩니다. removeSelf()나 display.remove() 와 개념이 유사합니다. 괄호 안에는 객체 이름이나 그 객체의 index 번호를 넣으면 됩니다.
어떤 display object를 remove 할 때 그 객체를 nil 로 만드는 것을 잊지 마세요. 효율적으로 메모리를 사용하는데 중요한 사항입니다.

Group Properties

group도 display object이기 때문에 이 그룹도 일반 display object들이 가지는 일반적인 메소드나 프로퍼티들을 가지고 있습니다. 그리고 numChildren이라는 프로퍼티를 덤으로 가지고 있구요.
이 numChildren은 그룹 내에 몇개의 객체들이 있는지 쉽게 알수 있게 해 줍니다. 이 프로퍼티는 for문등 루프를 돌릴 때 주로 사용 됩니다.
아래 예제는 그룹내의 모든 object들에 대해 어떤 값을 변경하기 위해 루핑 하는 방법을 보여줍니다.
-- This example assumes you have already inserted
-- several objects into the 'group2' display group. -- forward iteration
for i=1,group2.numChildren do
  local child = group2[i]   child.score = child.score + 1
end
-- backwards iteration; useful for removing objects manually
for i=group2.numChildren,1,-1 do
  local child = group2[i]   totalScore = totalScore + child.score
  child:removeSelf()
end
Property Differences

Group과 일반 이미지 객체와 몇가지 다른 점이 있습니다.
가장 중요한 다른 점은 default reference point가 다르다는 겁니다. 그룹은 display.TopLeftReferencePoint가 디폴트이고 다른 display object는 display.CenterReferencePoint가 디폴트입니다.
그 다음은 contentWidth와 contentHeight인데 당연히 group은 자체적으로 어떤 모양이 없기 때문에 고정된 width와 height가 없습니다. 그 child 중에 가장 큰 object의 값을 가질 겁니다.

Wrap-Up and Further Reading

지금까지 정리한 내용만 충분히 이해하면 코로나 Display Group에 대해서는 전문가가 되신 겁니다.
이번 튜토리얼은 basic 내용이긴 하지만 기본에 충실한 것이 좋은 앱을 만드는 바탕이 됩니다.

~~~~~~~~~~
반응형

American idol 과 밀알 장애인 선교단

2012. 2. 29. 22:31 | Posted by 솔웅


반응형
한국에서도 오디션 프로그램이 아주 인기있다고 하던데요. 여기 미국에서도 요즘 American Idol 이 아주 인기리에 방영되고 있습니다.

작년까지 American Idol에서 독설가 역을 맡았던 Simon이 X-Factor란 새로운 프로그램을 만들어서 나갔지만 제니퍼 로페즈, 에어로 스믹스의 스티븐 테일러, 그리고 잘 모르지만 랜디 잭슨이라는 심사위원이 재밌게 잘 이끌어가고 있습니다.

작년인가 재작년에는 한국계로 존 박이 24강까지 진출했던 것 같은데 올해는 한희준이라는 한인이 24강까지 진출했습니다.



여기를 누르시면 아메리칸 아이돌 웹사이트의 한희준군 페이지로 가실 수 있습니다.

요새 조금 바쁘게 지내다 보니까 아메리칸 아이돌을 못 보고 지나치는 경우가 많아서 그냥 한국계가 24강에 올라갔다 라는 정도만 인식하고 있었습니다.
그런데 한희준군이 인터뷰에서 출전 동기가 출연료를 받으면 밀알 장애인 선교단에 헌금을 좀 더 많이 할 수 있을것 같아서라고 했다는 소리를 들었습니다.
그리고 지지난주인가에 American Idol 프로그램에 한희준군이 밀알 선교단에서 장애인 아이들에게 춤과 노래를 가르쳐주는 장면이 잠깐 나왔구요.

이 얘기를 들으면서 좀 더 관심이 가더라구요.

한희준군은 뉴욕 플러싱에 살고 있어서 뉴욕 밀알 장애인 선교단 에서 활동하고 있고 저는 뉴저지에 살고 있어서 뉴저지 밀알 장애인 선교단에 그냥 가끔 들르고(^^) 있거든요.

뉴저지 밀알 모임에서 목사님으로부터 가끔 이 아메리칸 아이돌과 한희준군에 대한 소식을 전해 듣습니다.

그래서 좀 더 관심이 가더라구요.

어제는 24강중에 남자 출연자들이 콘테스트를 벌였는데 보지는 못했어요. 바로 밀알 모임일이어서...
오늘은 꼭 봐야겠어요. 24명중에 13명을 뽑는것 같던데 한희준군이 13명중에 뽑혔으면 좋겠습니다.

예전에 보면 이 13명중에 뽑히면 나중에 아메리칸 아이돌 끝난 후에 하는 미국 전국 투어 공연에 참여하게 되더라구요.
완전 미국 인기 연예인이 되는거예요.

밀알 선교단은 한국에서 소외받는 장애인들을 위한 일을 하려고 만들어진 건데요. 세계 각 국에도 밀알 선교단이 있어서 대개 현지 한인 장애인들을 위해 일을 하고 있습니다.

이곳 미국에도 뉴욕, 뉴저지, 워싱턴, LA 등 여러곳에서 밀알 선교단이 활동중으로 알고 있구요. 캐나다나 남미도 밀알이 현지 장애인들을 위해서 아주 좋은 일들을 많이 하고 있습니다.

위 사진은 지난 설날 때 윷놀이하고 찍은 뉴저지 밀알 가족 단체 사진이예요.
자체 건물이 없어서 저 교회도 장애인 선교에 뜻을 같이하는 다른 교회가 장소 사용을 허락하셔서 일주일에 한번 잘 이용하고 있습니다.

한국에서 특히 돈과 권력을 섬기는 큰 교회들이 많이 있어서 일반인들에게 많은 비판을 받고 있는 것으로 알고 있습니다.
저도 비판 받아 싸다고 생각하고 있구요. 비판을 넘어서 그런 모습들은 쓸어서 없애 버려야 한다고 생각하구요.
하여간 그래서 기독교가 아니라 개독교라고 교회 전체가 싸잡아 비난을 받고 있지만 그런 비난 속에서도 이렇게 소외받는 힘없는 사람들을 위해 일하는 많은 분들이 있다는 걸 알아 주세요.

뉴저지, 뉴욕 밀알 선교단 홈페이지 링크 걸어 놨으니 그들이 어떤 활동을 하고 있는지 관심 있으신 분들 한번 봐 주세요.

그리고 한국에도 여러분 주위에 이런 좋은 활동하는 단체와 사람들이 분명 있을 테니 잠깐만이라도 그쪽으로 눈길 한번 돌리는 시간을 가져 주시면 좋겠습니다.

이번주 American Idol에 한희주 군이 꼭 Top 13에 뽑혔으면 좋겠습니다.
~~~~~~~~~~
반응형