Android/App개발2013. 3. 7. 11:20


안드로이드 Bluetooth 프로그래밍을 위해 http://developer.android.com/guide/topics/connectivity/bluetooth.html의 내용을 정리함.

안드로이드 플랫폼은 블루투스네트웍스택을 제공하여 다음의 블루투스의 기능을 제공한다.

- 다른 디바이스의 스캔

- 페어링을 위해 로칼디바이스에 쿼리 

- RFCOMM채널 연결 가능

- 서비스 디스커버리를 통한 타디바이스 연결

- 디바이스간 데이터 전송

- 다중연결 관리


기초

블루투스셋업, 페어링된 또는 유효한 디바이스 찾기, 연결하기 그리고 데이터 전송하기에 대한 기초적인 내용을 기술하겠다.

android.bluetooth패키지에 모든 API들이 정의되어 있다. 다음에 설명되는 클래스들이 대표적인 클래스들이다.

BluetoothAdapter

  로컬블루투스어댑터를 대표한다. 이 객체를 통해서 디바이스를 찾고 연결하고 디바이스를 활성화하여 통신을 수행하는등의 작업이 가능하다.

BluetoothDevice

  원격블루투스디바이스를 대표한다. 이 객체를 통해 블루투스소켓을 통한 연결이나 디바이스의 속성정보를 요청할 수 있다.

BluetoothSocket

  블루투스소켓의 인터페이스를 대표한다. TCP socket과 유사하다. 입출력스트림을 통해 다른 디바이스와 데이터를 주고받을 수 있다.

BluetoothServerSocket

  들어오는 요청을 수신할 수 있는 서버소캣을 대표한다. TCP ServerSocket과 유사하다. 

BluetoothClass

  블루투스디바이스의 특성과 사양을 나타낸다. 읽기전용속성이며 major, minor 디바이스 클래스로 디바이스의 성격을 규정한다.  

BluetoothProfile

  블루투스프로파일을 나타낸다. 블루투스기반의 통신을 위한 무선인터페이스 규격을 말한다. 예를 들어 핸즈프리 프로파일같은 것을 말한다. 더 자세한 내용은 http://developer.android.com/guide/topics/connectivity/bluetooth.html#Profiles 를 참조하라.

BluetoothHeadset

  모바일폰을 위한 블루투스헤드셋을 지원한다. 핸즈프리(v1.5)프로파일을 포함한다.

BluetoothA2dp

  오디오전송을 위한 품질을 정의한다. A2DP(Advanced Audio Distribution Profile)

BluetoothHealth

  건강관련 디바이스 프로파일

BluetoothHealthCallback

  BluetoothHealth 콜백을 구현하기 위한 추상클래스. 애플리케이션의 등록상태나 블루투스채널의 변경이나 업데이트의 수신을 감지하기 위해 이 클래스를 반드시 구현해야 한다.

BluetoothHealthAppCOnfiguration

  Bluetooth Health 3rd-party애플리케이션을 health device에 연결하기 위한 설정

BluetoothProfile.ServiceListener

  Bluetooth 서비스가 연결 또는 차단되었을 때 BluetoothProfile IPC 클라이언트에 통지를 한다. 즉 내부에서 서비스는 각각의 프로파일로 동작한다.


블루투스 권환

BLUETOOTH와 BLUETOOTH_ADMIN 권한이 준비되어 있다.

BLUETOOTH : 블루투스통신을 위한 권한

BLUETOOTH_ADMIN :  디바이스 디스커버리나 장치설정등을 할 수 있는 권한.


블루투스 셋업

블루투스를 사용하기 전에 디바이스가 블루투스가 가능한지 살펴야 한다.

1. BluetoothAdapter를 얻어낸다.

  1. BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
    if (mBluetoothAdapter == null) {
       
    // Device does not support Bluetooth
    }

2. 블루투스를 활성화한다.

if (!mBluetoothAdapter.isEnabled()) {
   
Intent enableBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
    startActivityForResult
(enableBtIntent, REQUEST_ENABLE_BT);
}

위와 같이 호출하면 블루투스활성화여부 팝업이 뜨며 사용자가 확인을 선택하면 RESULT_OK가 onActivityResult()에서 리턴되고 그러지 않으면 RESULT_CANCELED가 리턴된다.

이 때 앱은 onPause와 onResume루틴을 타게 되니 유의한다.


디바이스 찾기

BluetoothAdapter를 통해 근처의 디바이스들을 찾을 수 있으며 페어링된 다바이스리스트도 얻을 수 있다.

디바이스디스커버리는 주변의 블루투스가 활성화되어 있는 디바이스들을 스캔한다. 그리고 각 디바이스의 정보를 수집한다. 수집이 가능한 디바이스는 블루투스가 활성화되어 있고 검색가능한 상태로 셋팅되어 있어야 한다. 그러면 검색되어 지고 디바이스명, 클래스 그리고 MAC주소가 수집이 가능하다. 이 정보를 기반으로 특정 디바이스를 선택하여 연결할 수 있다.

한번 연결이 이루어진 디바이스는 자동으로 페어링이 요청되게 된다. 디스커버리할때 저장된 MAC주소정보를 통해 디서커버리 단계 없이 바로 연결을 할 수 있다.

페어링과 연결은 한가지 차이점이 있다. 페어링은 두 디바이스가 서로 존재함을 인식하는 것이고 인증을 위해 공유된 link-key를 가지고 서로 암호화된 연결을 확립할 수 있다.

연결은 디바이스간에 RFCOMM채널을 공유하였다는 것이고 서로 데이터를 주고 받을 수 있다는 것이다. 페어링이 먼저 되어야 통신을 수행할 수 있다.

안드로이드가 탑재된 제품들은 discoverable이 꺼진 상태이다. 따라서 페어링을 위해 discoverable을 활성화하여야 한다. http://developer.android.com/guide/topics/connectivity/bluetooth.html#EnablingDiscoverability 를 참조하라.


페어링된 다바이스 얻기

디스커버리를 수행하기 전에 현재 페어링된 디바이스를 알아낼수 있다. getBondedDevices()메서드로 페어링된 디바이스들의 리스트를 얻을 수 있다.

Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
// If there are paired devices
if (pairedDevices.size() > 0) {
   
// Loop through paired devices
   
for (BluetoothDevice device : pairedDevices) {
       
// Add the name and address to an array adapter to show in a ListView
        mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
   
}
}

검색된 디바이스에는 연결을 위한 MAC주소정보가 포함되어 있으며 연결하기는 다음을 참조하라

http://developer.android.com/guide/topics/connectivity/bluetooth.html#ConnectingDevices


디바이스 디스커버리

startDiscovery()메서드를 수행하면 된다. 프로세스는 비동기로 동작하고 메서드는 바로 리턴된다. 

비동기로 진행되는 과정에 디바이스가 검색이 되면 ACTION_FOUND 인텐트가 브로드캐스트 되며 이 이벤트를 받기 위해 BroadcastReceiver를 사용해야 한다.

// Create a BroadcastReceiver for ACTION_FOUND
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
   
public void onReceive(Context context, Intent intent) {
       
String action = intent.getAction();
       
// When discovery finds a device
       
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
           
// Get the BluetoothDevice object from the Intent
           
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
           
// Add the name and address to an array adapter to show in a ListView
            mArrayAdapter
.add(device.getName() + "\n" + device.getAddress());
       
}
   
}
};
// Register the BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver
(mReceiver, filter); // Don't forget to unregister during onDestroy

디스커버리 과정은 전원을 많이 소비사는 과정이므로 검색이 다 끝나면 반드시 cancelDiscovery()를 호출해주어야 한다. 그리고 연결이 되어 있는 상태에서 디스커버리는 통신데이터 bandwidth를 현저하기 떨어뜨리기 때문에 연결중에는 디스커버리를 하지 않아야 한다.

디스커버러블 활성화

ACTION_REQUEST_DISCOVERABLE 액션인텐트를 startActivityForResult로 호출한다. EXTRA_DISCOVERABLE_DURATION 으로 탐색시간을 파라메터로 넘길 수 있다. 최대시간은 3600초이고 0이하는 내부적으로 120초로 자동 셋팅된다. 다음은 300초로 셋팅한 예이다.

Intent discoverableIntent = new
Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
discoverableIntent
.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity
(discoverableIntent);

디바이스가 블루투스가 활성화가 안되어 있다면 자동으로 활성화후 진행한다.

디스커버러블모드는 백그라운드에서 지정한 시간만큼 유지되었다가 끝나게 된다. 만일 이 상태변화를 처리하고 싶다면 ACTION_SCAN_MODE_CHANGED인텐트의 리시버를 통해서 처리하면 된다. 이 인텐트는 추가정보로 EXTRA_SCAN_MODE, EXTRA_PREVIOUS_SCAN_MODE로 현재와 이전 스캔모드를 얻을 수 있다. 

모드는 3가지이다.

- SCAN_MODE_CONNECTABLE_DISCOVERABLE :    디바이스가 둘다 가능한 상태

- SCAN_MODE_CONNECTABLE :  디스커버러블모드는 아니고 연결은 가능한 상태.

-  SCAN_MODE_NONE : 둘다 안되는 상태

디바이스와 이미 연결이 초기화된 상태이면 굳이 디스커버러블을 활성화할 필요는 없다. 디스커버러블을 활성화하는 경우는 앱이 서버소켓을 제공하여 연결시도를 허락하기 위한 경우만 해당된다. 왜냐하면 원격디바이스들은 연결이 가능해지기 이전에 먼저 검색이 가능해야 하기 때문이다.


디바이스 연결

앱이 두단말간에 연결을 생성하기 위해서 서버와 클라이언트단을 모두 구현해야 한다. 하나는 서버가 되어서 대기하고 하나는 연결을 시도해야 하기 때문이다. 연결시 사용하는 정보는 서버의 MAC주소가 된다. 서버와 단말이 둘다 동일 RFCOMM채널상에서 BluetoothSocket으로 연결되면 상호간에 연결된것으로 간주된다. 이 때 입출력스트림을 통해 데이터를 주고받을 수 있다. 

서버와 클라이언트 디바이스는 각각 다른 방식으로 요구되는 BluetoothSocket을 얻는다. 서버는 요청받은 연결을 허락했을 때 얻어지고 클라이언트는 서버와 RFCOMM채널이 열렸을 때 얻어진다.

두 단말이 모두 자동으로 서버모드로 대기하고 서로간에 연결을 시도하는 방식과 명시적으로 하나는 호스트로써 서버로 대기하고 다른 하나가 연결을 시도하는 방식이 있다.

두 디바이스가 페어링된적이 없으면 안드로이드는 자동으로 페어링요청 통지나 팝업을 연결과정에 띄운다. 그래서 연결시도과정에 두 단말이 페어링된적이 있는지 알 필요가 없다. 연결시도는 페어링과정이 끝난 이후 계속 진행된다.


서버 연결

BluetoothServerSocket으로 서버로 동작시킬 수 있다. 서버소켓은 연결요청을 받아들이기 위한것이다.

1. BluetoothServerSocket을 얻는다.

   lisetnUsingRfcommWithServiceRecord(String, UUID)로 얻는다. String는 서비스명이고 UUID는 클라이언트디바이스와 연결 동의에 기반으로 사용된다. 다시 말해 클라이언트디바이스가 연결을 시도할 때 UUID가 전달되어진다. 다음단계에서 UUID는 서로 맷치가 되어야 한다.

2. accept()메서드로 연결요청을 대기한다.

  연결이 허용되거나 예외가 발생할 때 까지 이 메서드는 블럭된다.  클라이언트의 연결요청이 UUID가 일치하였을 때만 accept가 성공하고 BluetoothSocket을 리턴한다.

3. 더이상 연결을 허용하지 않을거라면 close()를 호출한다.

  서버소켓을 종료하고 모든 리소스를 해재한다. 연결된 상태는 유지된다.  TCP/IP와 다르게 RFCOMM은 하나의 채널에 하나의 연결만 허용한다. 따라서 accept()로 연결이 성립되면 서버소켓을 close()로 닫아준다.

accept는 Main activity에서 호출하지 않아야 블럭되는것을 방지할 수 있다. 블럭중에 중단하려면 다른 스레드에서 close()를 호출하면 된다.

다음은 서버구현의 예이다.

private class AcceptThread extends Thread {
   
private final BluetoothServerSocket mmServerSocket;
 
   
public AcceptThread() {
       
// Use a temporary object that is later assigned to mmServerSocket,
       
// because mmServerSocket is final
       
BluetoothServerSocket tmp = null;
       
try {
           
// MY_UUID is the app's UUID string, also used by the client code
            tmp
= mBluetoothAdapter.listenUsingRfcommWithServiceRecord(NAME, MY_UUID);
       
} catch (IOException e) { }
        mmServerSocket
= tmp;
   
}
 
   
public void run() {
       
BluetoothSocket socket = null;
       
// Keep listening until exception occurs or a socket is returned
       
while (true) {
           
try {
                socket
= mmServerSocket.accept();
           
} catch (IOException e) {
               
break;
           
}
           
// If a connection was accepted
           
if (socket != null) {
               
// Do work to manage the connection (in a separate thread)
                manageConnectedSocket
(socket);
                mmServerSocket
.close();
               
break;
           
}
       
}
   
}
 
   
/** Will cancel the listening socket, and cause the thread to finish */
   
public void cancel() {
       
try {
            mmServerSocket
.close();
       
} catch (IOException e) { }
   
}
}


클라이언트 연결

리모트디바이스와 연결하기 위해서는 일단 리모트디바이스객체를 얻어야 한다. 페어링된 디바이스를 얻거나 디스커버리 과정을 통해서 연결하고자 하는 BluetoothDevice를 얻어야 한다.

1. BluetoothDevice.createRfcommSocketToServiceRecord(UUID) 를 호출

  BluetoothSocket을 초기화한다. 이 때 서버디바이스에서 제공하는 UUID를 인자로 넘긴다. 이 값이 동일해야 연결이 성립된다.

2. connect()로 연결 시도

  UUID가 매칭되는 디바이스와 연결을 시도하고 성공하면 소켓을 리턴한다. 

connect()메서드가 블럭되는 호출이기 때문에 분리된 스레드에서 수행되어야 한다.

connect()과정 중에는 디스커버리가 진행되면 안된다. 만일 진행하게 되면 연결과정이 아주 느리게 진행되거나 실패하게 된다.

다음은 간략한 예제이다.

private class ConnectThread extends Thread {
   
private final BluetoothSocket mmSocket;
   
private final BluetoothDevice mmDevice;
 
   
public ConnectThread(BluetoothDevice device) {
       
// Use a temporary object that is later assigned to mmSocket,
       
// because mmSocket is final
       
BluetoothSocket tmp = null;
        mmDevice
= device;
 
       
// Get a BluetoothSocket to connect with the given BluetoothDevice
       
try {
           
// MY_UUID is the app's UUID string, also used by the server code
            tmp
= device.createRfcommSocketToServiceRecord(MY_UUID);
       
} catch (IOException e) { }
        mmSocket
= tmp;
   
}
 
   
public void run() {
       
// Cancel discovery because it will slow down the connection
        mBluetoothAdapter
.cancelDiscovery();
 
       
try {
           
// Connect the device through the socket. This will block
           
// until it succeeds or throws an exception
            mmSocket
.connect();
       
} catch (IOException connectException) {
           
// Unable to connect; close the socket and get out
           
try {
                mmSocket
.close();
           
} catch (IOException closeException) { }
           
return;
       
}
 
       
// Do work to manage the connection (in a separate thread)
        manageConnectedSocket
(mmSocket);
   
}
 
   
/** Will cancel an in-progress connection, and close the socket */
   
public void cancel() {
       
try {
            mmSocket
.close();
       
} catch (IOException e) { }
   
}
}


연결관리

디바이스간 연결이 완료되면 BluetoothSocket을 각각 소유하게 된다. 이넘을 통해서 데이터를 주고받을 수 있다.

1. 소켓으로부터 InputStream과 OutputStream을 얻는다.

2.  read(byte[]), write(byte[])로 읽거나 쓰면 끝...

두 메서드 모두 블럭되기 때문에 스레드로 분리되어야 한다.

private class ConnectedThread extends Thread {
   
private final BluetoothSocket mmSocket;
   
private final InputStream mmInStream;
   
private final OutputStream mmOutStream;
 
   
public ConnectedThread(BluetoothSocket socket) {
        mmSocket
= socket;
       
InputStream tmpIn = null;
       
OutputStream tmpOut = null;
 
       
// Get the input and output streams, using temp objects because
       
// member streams are final
       
try {
            tmpIn
= socket.getInputStream();
            tmpOut
= socket.getOutputStream();
       
} catch (IOException e) { }
 
        mmInStream
= tmpIn;
        mmOutStream
= tmpOut;
   
}
 
   
public void run() {
       
byte[] buffer = new byte[1024];  // buffer store for the stream
       
int bytes; // bytes returned from read()
 
       
// Keep listening to the InputStream until an exception occurs
       
while (true) {
           
try {
               
// Read from the InputStream
                bytes
= mmInStream.read(buffer);
               
// Send the obtained bytes to the UI activity
                mHandler
.obtainMessage(MESSAGE_READ, bytes, -1, buffer)
                       
.sendToTarget();
           
} catch (IOException e) {
               
break;
           
}
       
}
   
}
 
   
/* Call this from the main activity to send data to the remote device */
   
public void write(byte[] bytes) {
       
try {
            mmOutStream
.write(bytes);
       
} catch (IOException e) { }
   
}
 
   
/* Call this from the main activity to shutdown the connection */
   
public void cancel() {
       
try {
            mmSocket
.close();
       
} catch (IOException e) { }
   
}
}


프로파일로 동작하기

안드로이드 3.0부터 블루투스프로파일로 동작이 가능해졌다. 블루투스프로파일은 무선인터페이스 규격으로 블루투스기반의 디바이스간 통신에 기반한것이다. 예를 들어 헨즈프리프로파일같은 것이 있는데 모바일폰과 헤드셋간의 연결이 가능하려면 두 디바이스 모두 핸즈프리 프로파일을 지원해야 한다.

블루투스프로파일을 직접 커스텀하게 작성이 가능하다. 안드로이드 블루투스 API는 다음의 프로파일의 구현을 지원한다.

- 헤드셋

- A2DP

- Health Device

프로파일기반으로 동작시키려면 다음과정으로 진행한다.

1. 블루투스어댑터를 얻는다.

2. 프로파일 프록시 객체를 얻는다 getProfileProxy(). 이 객체로 연결을 달성하게 된다. 아래 예제에서는 BluetoothHeadset의 인스턴스를 프록시객체로 사용하였다.

3. BluetoothProfile.ServiceListener를 셋업하여 연결과 해재 이벤트를 처리한다.

4. onServiceConnected()에서 프록시객체의 핸들을 얻는다.

5. 프록시객체를 얻으면 이 객체를 통하여 연결상태를 모니터링하고 해당 프로파일과 연관된 동작들을 수행할 수 있다.

다음은 BluetoothHeadset 프록시객체를 사용하여 해드셋프로파일을 제어하는 예이다.

BluetoothHeadset mBluetoothHeadset;
 
// Get the default adapter
BluetoothAdapter mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
 
// Establish connection to the proxy.
mBluetoothAdapter
.getProfileProxy(context, mProfileListener, BluetoothProfile.HEADSET);
 
private BluetoothProfile.ServiceListener mProfileListener = new BluetoothProfile.ServiceListener() {
   
public void onServiceConnected(int profile, BluetoothProfile proxy) {
       
if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset
= (BluetoothHeadset) proxy;
       
}
   
}
   
public void onServiceDisconnected(int profile) {
       
if (profile == BluetoothProfile.HEADSET) {
            mBluetoothHeadset
= null;
       
}
   
}
};
 
// ... call functions on mBluetoothHeadset
 
// Close proxy connection after use.
mBluetoothAdapter
.closeProfileProxy(mBluetoothHeadset);


Posted by 삼스
iOS2013. 1. 15. 19:58


http://cafe.daum.net/iLogic/915W/20?docid=1KHkj915W2020110321201150

PDF 문서 생성, 출력, 변환

PDF 문서는 소형 프로그래밍 언어로 작성된 명령들로 해상도 독립 벡터 그래픽, 텍스트, 이미지를 저장한다. PDF 문서는 이미지와 텍스트를 여러 페이지 포함할 수 있다. PDF 는 크로스 플랫폼, 읽기 전용 문서, 해상도 독립 그래픽 드로잉에 유용하다.

모든 어플에 대해 Quartz 는 그림 13-1 처럼 어플의 드로잉 연산을 보존하는 충실도가 높은 PDF 문서를 생성한다. 결과 PDF 는 시스템의 다른 일부나 외우 업체 제품으로 특정 프린터나 웹을 위해 특정한 목적으로 최적화될 수도 있다. Quartz 에서 생성된 PDF 문서는 미리보기나 Acrobat 에서 볼 수 있다.

그림 13-1  Quartz 는 고해상도 PDF 문서를 생성한다.

Quartz 는 "디지털 종이" 로 PDF 를 사용할 뿐만 아니라 PDF 를 표시하고 생성하고, PDF 관련 작업들을 사용할 수 있는 수많은 API 함수중 일부를 포함하고 있다. 다음 내용을 다룬다.

PDF 언어와 문법을 포함한 PDF 에 대한 자세한 정보는 PDF 레퍼런스 네 번째 판인 버전 1.5 를 참고해라.

PDF 열기와 화면 출력

Quartz 는 PDF 문서를 표현하기 위해 CGPDFDocumentRef 데이터 타입을 제공한다.CGPDFDocumentCreateWithProvider 함수나 CGPDFDocumentCreateWithURL 함수를 사용하여 CGPDFDocument 객체를 생성해야 한다. CGPDFDocument 객체를 생성한 후, 그래픽 콘텍스트에 드로잉할 수 있다. 그림 13-2 는 PDFViewer 샘플 어플에서 표시된 PDF 문서를 보여준다. Xcode 도구 CD 를 인스톨할 후, 이 어플의 Xcode 프로젝트를 다음 위치에서 찾을 수 있다.

/Developer/Examples/Quartz/PDF/PDFViewer

그림 13-2  PDFViewer 샘플 어플에 의해 표시된 PDF 문서

코드 13-1 은 CGPDFDocument 객체를 생성하고 문서내의 페이지수를 얻는 방법을 보여준다.

코드 13-1  PDF 파일로부터 CGPDFDocument 객체를 생성하는 함수

CGPDFDocumentRef MyGetPDFDocumentRef (const char *filename)
{
    CFStringRef path;
    CFURLRef url;
    CGPDFDocumentRef document;
 
    path = CFStringCreateWithCString (NULL, filename,
                         kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path, // 1
                        kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    document = CGPDFDocumentCreateWithURL (url);// 2
    CFRelease(url);
    count = CGPDFDocumentGetNumberOfPages (document);// 3
    if (count == 0) {
        printf("`%s' needs at least one page!", filename);
        return NULL;
    }
    return document;
}

코드 설명:

  1. 디스플레이할 PDF 파일의 파일명을 나타내는 CFString 객체로 부터 CFURL 객체를 생성하기 위해 코어 파운데이션 함수를 호출해야 한다.

  2. CFURL 객체로 부터 CGPDFDocument 객체를 생성한다.

  3. 다음 코드에서 문서가 최소 한 페이지를 갖고 있는지 확인하기 위해서 PDF 의 페이지 수를 얻는다.

코드 13-2는 PDF 페이지를 그래픽 콘텍스트에 드로잉하는 방법을 보여준다.

코드 13-2  PDF 페이지를 드로잉하는 함수

void MyDisplayPDFPage (CGContextRef myContext,
                    size_t pageNumber,
                    const char *filename)
{
    CGPDFDocumentRef document;
    CGPDFPageRef page;
    CGRect box;
 
    document = MyGetPDFDocumentRef (filename);// 1
    page = CGPDFDocumentGetPage (document, pageNumber);// 2
    CGContextDrawPDFPage (myContext, page);// 3
    CGPDFDocumentRelease (document);// 4
}

코드 설명:

  1. 대입한 파일이름으로 부터 CGPDFDocument 객체를 생성하기 위해 함수를 호출한다.

  2. PDF 문서로 부터 명시한 페이지 번호의 페이지를 얻는다.  

  3. CGContextDrawPDFPage 함수를 호출하여 PDF 파일로 부터 명시한 페이지를 드로잉한다. 그래픽 콘텍스트와 드로잉할 페이지를 대입해야만 한다. iPhone OS 나 Mac OS X v10.3 이후 버전에서 어플을 수행하려면, 이전의CGContextDrawPDFDocument 함수대신에 이 함수를 사용하는 것이 좋다.

  4. CGPDFDocument 객체를 해제한다.

PDF 페이지용 변환을 생성

Quartz 는 PDF 페이지의 한 상자를 대입한 사각형으로 매핑하는 affine 변환을 생성하는 CGPDFPageGetDrawingTransform함수를 제공한다. 이 함수의 형식은 다음과 같다.

CGAffineTransform CGPDFPageGetDrawingTransform (
        CGPPageRef page,
        CGPDFBox box,
        CGRect rect,
        int rotate,
        bool preserveAspectRatio
);

함수는 다음 알고리즘을 사용하여 affine 변환을 리턴한다.

  • 파라미터에 대입하는 media, crop, bleed, trim, art 와 같은 PDF 박스의 타입과 명시된 PDF 페이지의 /MediaBox 엔트리와 관련된 사각형을 교차시킨다. 교차하여 교집합 부분이 유효한 결과 사각형이 된다.

  • PDF 페이지의 /Rotate 엔트리에 의해 명시된 값에 의해서 유효 사각형을 회전시킨다.

  • 결과 사각형을 파라미터로 제공한 사각형의 중심으로 옮긴다.

  • 대입한 rotate 파라미터의 값이 0 이 아니고 90 의 배수면, 함수는 대입한 각도로 유효 사각형을 회전시킨다. 양수 값은 오른쪽으로 사각형을 회전시키며, 음수 값은 사각형을 왼쪽으로 회전시킨다. 전달하는 각도는 라디안 값이 아니다. PDF 페이지의 /Rotate 엔트리도 회전을 포함한다. 대입하는 rotate 파라미터는 /Rotate 엔트리와 조합된다.

  • 필요하면 유효 사각형을 확대/축소한다. 그래서, 대입한 사각형의 가장자리와 일치시킨다. 

  • preserveAspectRatio 파라미터에 true 를 전달하여 가로 세로의 비를 보존한다면, 마지막 사각형은 rect 파라미터에서 명시된 사각형의 제한된 가장자리와 일치시킨다.

예를 들어 그림 13-3 에서 보이는 PDF 파일을 보여주는 어플을 작성할 때 이 함수를 사용할 수 있다. 왼쪽과 오른쪽으로 회전하는 기능을 제공하고자 한다면, 현재 윈도우 크기와 회전하는 설정에 대한 적절한 변환을 계산하기 위해CGPDFPageGetDrawingTransform 를 호출할 수 있다.

그림 13-3  오른쪽으로 90 도 회전된 PDF 페이지

코드 13-3 은 함수에 전달된 파라미터를 사용하여 PDF 페이지용 affine 변환을 생성하는 함수가 변환을 적용하고 PDF 페이지를 드로잉하는 방법을 보여준다.

코드 13-3  PDF 페이지용 affine 변환 생성

void MyDrawPDFPageInRect (CGContextRef context,
                    CGPDFPageRef page,
                    CGPDFBox box,
                    CGRect rect,
                    int rotation,
                    bool preserveAspectRatio)
{
    CGAffineTransform m;
 
    m = CGPDFPageGetDrawingTransform (page, box, rect, rotation,// 1
                                    preserveAspectRato);
    CGContextSaveGState (context);// 2
    CGContextConcatCTM (context, m);// 3
    CGContextClipToRect (context,CGPDFPageGetBoxRect (page, box));// 4
    CGContextDrawPDFPage (context, page);// 5
    CGContextRestoreGState (context);// 6
}

코드 설명:

  1. 함수에 제공된 파라미터로 부터 affine 변환을 생성한다.

  2. 그래픽 상태를 저장한다.

  3. CTM 을 affine 변환과 연관시킨다.

  4. 그래픽 콘텍스트를 box 파라미터에 대입된 사각형으로 자른다. CGPDFPageGetBoxRect 함수는 대입한 상수kCGPDFMediaBoxkCGPDFCropBoxkCGPDFBleedBox,kCGPDFTrimBoxkCGPDFArtBox 과 관련된 (media, crop, bleed, trim, art box) 형 사각형을 경계로하는 페이지를 얻는다.

  5. PDF 페이지를 변환되고 잘린 콘텍스트에 드로잉한다.

  6. 그래픽 상태를 복원한다.

PDF 파일 생성

 PDF 파일을 그래픽 콘텍스트에 드로잉하는 것과 같이 Quartz 2D 를 사용하여 PDF 파일을 생성하는 것은 쉽다. PDF 파일의 위치를 명시하고, PDF 그래픽 콘텍스트를 설정하고, 다른 그래픽 콘텍스트에서 사용했던 동일한 드로잉 루틴을 사용하게 된다. 코드 13-4 의 MyCreatePDFFile 함수는 PDF 를 생성하기 위해 사용하는 모든 작업을 보여줄 것이다.

코드는 CGContextBeginPage 함수와 CGContextEndPage 함수를 호출하여 PDF 페이지를 묘사한다. iPhone OS 와 Mac OS X v10.4 이후 버전에서는, CGPDFContextBeginPage 함수와 CGPDFContextEndPage 함수를 대신 사용할 수 있다. 새로 추가된 함수들의 장점은 media, crop bleed, trim, art box 의 페이지 속성을 대입하기 위해 CFDictionary 를 전달할 수 있다는 점이다.

사전용 키 상수의 목록과 이들에 대한 정보는 CGPDFContext 레퍼런스 를 참고해라.

코드 13-4  PDF 를 생성하는 함수

void MyCreatePDFFile (CGRect pageRect, const char *filename)// 1
{
    CGContextRef pdfContext;
    CFStringRef path;
    CFURLRef url;
    CFMutableDictionaryRef myDictionary = NULL;
 
    path = CFStringCreateWithCString (NULL, filename, // 2
                                kCFStringEncodingUTF8);
    url = CFURLCreateWithFileSystemPath (NULL, path, // 3
                     kCFURLPOSIXPathStyle, 0);
    CFRelease (path);
    myDictionary = CFDictionaryCreateMutable(NULL, 0,
                        &kCFTypeDictionaryKeyCallBacks,
                        &kCFTypeDictionaryValueCallBacks); // 4
    CFDictionarySetValue(myDictionary, kCGPDFContextTitle, CFSTR("My PDF File"));
    CFDictionarySetValue(myDictionary, kCGPDFContextCreator, CFSTR("My Name"));
    pdfContext = CGPDFContextCreateWithURL (url, &pageRect, myDictionary); // 5intent 
    CFRelease(myDictionary);
    CFRelease(url);
    CGContextBeginPage (pdfContext, &pageRect); // 6
    myDrawContent (pdfContext);// 7
    CGContextEndPage (pdfContext);// 8
    CGContextRelease (pdfContext);// 9
}

코드 설명:

  1. PDF 페이지의 크기를 나타내는 사각형과 파일명을 나타내는 문자열을 파라미터로 받는다.

  2. MyCreatePDFFile 함수에 전달된 파일명으로 부터 CFString 객체를 생성한다.

  3. CFString 객체로 부터 CFURL 객체를 생성한다.

  4. 메타데이터를 얻기 위해서 빈 CFDictionary 객체를 생성한다. 다음 두 줄은 타이틀과 작가 이름을 추가하는 것이다.CFDictionarySetValue 함수를 사용하여 여러 키-값 쌍을 추가할 수 있다. 사전을 생성하는데 대한 자세한 정보는CFDictionary 레퍼런스 을 참고해라.

  5. 다음의 3 개의 파라미터를 전달하여 PDF 그래픽 콘텍스트를 생성한다.

    • PDF 데이터의 위치를 나타내는 CFURL 객체.

    • PDF 페이지의 기본 크기와 위치를 정의하는 사각형에 대한 포인터. 사각형의 원점은 일반적으로 (0, 0) 이다. Quartz 는 이 사각형을 페이지의 미디어 박스의 기본 영역으로 사용한다. NULL 을 전달하면, Quartz 는 8.5 x 11 인치(612 x 792 픽셀)의 기본 페이지 크기를 사용한다.

    • PDF 메타데이터를 포함하는 CFDictionary 객체. 추가할 메타데이터가 없다면 NULL 을 전달한다. 

      iPhone OS 와 Mac OS X v10.4 에서는 부분 형식, 조건, 조건 식별자, 레지스트리 이름, 목적 파일 프로필, 목적 장치나 제조 조건에 관한 추가 정보나 설명을 포함하는 사람이 읽ㅅ을 수 있는 문자열 등의 출력 옵션을 표시하기 위해 CFDictionary 객체를 사용할 수 있다. 출력 옵션에 대한 자세한 정보는 CGPDFContext 레퍼런스를 참고해라.

  6. 페이지의 시작을 나타낸다. PDF 와 같은 다중 페이지를 제공하는 그래픽 콘텍스트를 사용할 때, 출력에서 페이지의 경계를 나타내기 위해 CGContextEndPage 와 CGContextBeginPage 함수를 호출해야 한다. Quartz 는 페이지 기반 콘텍스트에서 페이지의 경계를 넘어가는 외부에서 실행된 모든 드로잉 연산은 무시한다. 

    iPhone OS 와 Mac OS X v10.4 이후 버전에서, 페이지의 속성을 정의하기 위해 키-값 쌍을 포함하는 그래픽 콘텍스트와 CFDictionary 를 제공하는 CGPDFContextBeginPage 함수를 대신 사용할 수 있다.

  7. PDF 콘텍스트에 콘텢츠를 드롱이하기 위해 어플에서 정의한 함수를 호출한다. 여기에 드로잉 루틴들을 작성하면 된다.

  8. 페이지 기반 그래픽 콘텍스트에 페이지가 끝났다고 알린다.

    iPhone OS 와 Mac OS X v10.4 이후 버전에서 이전에 CGPDFContextBeginPage 함수를 호출했다면, 페이지의 끝을 알리기 위해 CGPDFContextEndPage 함수를 사용해야만 했다.

  9. PDF 콘텍스트를 해제한다.

링크 추가

생성한 PDF 콘텍스트에 링크와 지점을 추가할 수 있다. iPhone OS 와 Mac OS X v10.4 부터, Quartz 는 링크에 관한 정보와 파라미터로 PDF 그래픽 콘텍스트를 갖는 다음의 3 가지 함수를 제공한다.

  • CGPDFContextSetURLForRect 는 현재 PDF 페이지에서 사각형을 클릭할 때 오픈할 URL 을 명시하도록 한다.

  • CGPDFContextSetDestinationForRect 는 현재 PDF 페이지에서 사용자가 사각형을 클릭할 때 목적지로 이동하도록 설정한다. 목적지 이름을 명시해야 한다.

  • CGPDFContextAddDestinationAtPoint 는 현재 PDF 페이지에서 점을 클릭할 때 이동할 목적지를 설정하도록 한다. 목적지 이름을 명시해야 한다.

PDF 콘텐츠 보호

iPhone OS 와 Mac OS X v10.4 부터 가능한 PDF 콘텐츠를 보호하려면, CGPDFContextCreate 함수에 전달할 보조 사전에서 명시할 수 있는 여러 보안 옵션들이 존재한다. 소유자 패스워드, 사용자 패스워드, PDF 가 보조 사전에 다음 키를 포함하여 복사될 수 있는지 프린트될 수 있는지를 설정할 수 있다. 

  • kCGPDFContextOwnerPassword 는 PDF 문서의 소유자 패스워드를 정의할 때 사용한다. 이 키가 대입되면, 문서는 소유자 패스워드를 값으로 사용하여 암호화된다. 그렇지 않으면, 문서는 암호화되지 않는다. 이 키 값은 ASCII 로 표현될 수 있는 CFString 객체가 되어야 한다. 처음 32 바이트만 패스워드로 사용된다. 이 키의 디폴트 값은 없다. 이 키의 값이 ASCII 로 표현될 수 없다면, 문서는 생성되지 않으며 생성 함수는 NULL 을 리턴한다. iPhone OS 와 Mac OS X v10.4 에서 Quartz 는 40 비트 암호화를 사용한다.

  • kCGPDFContextUserPassword 는 PDF 문서의 사용자 패스워드를 정의한다. 문서가 암호화되면, 이 키 값은 문서의 사용자 패스워드가 된다. 이 값이 명시되지 않으면 사용자 패스워드는 빈 문자열이 된다. 이 키 값은 ASCII 암호화로 표현될 수 있는 CFString 객체가 되어야 한다. 첫 번째 32 바이트만 패스워드로 사용된다. 이 키 값이 ASCII 로 표현될 수 없으면, 문서는 생성되지 않고, 생성함수는 NULL 을 리턴한다.

  • kCGPDFContextAllowsPrinting 는 문서가 사용자 패스워드로 비공개가 아닐 때 프린트할 수 있는지를 나타낸다. 이 키 값은 CFBoolean 객체가 되어야 한다. 이 키의 디폴드 값은 kCFBooleanTrue 이다.

  • kCGPDFContextAllowsCopying 는 문서가 사용자 패스워드로 비공개가 아닌 경우 복사될 수 있는지를 나타낸다. 이 키 값은 CFBoolean 객체이어야 한다. 이 키의 디폴트 값은 kCFBooleanTrue 이다.

다음 강좌의 코드 14-4 는 PDF 문서가 공개인지 비공개인지를 검사하고, 패스워드를 사용하여 문서를 오픈하는 코드를 보여줄 것이다.

 

Posted by 삼스
iOS2013. 1. 15. 17:11


출처 : http://blog.naver.com/PostView.nhn?blogId=gigar&logNo=60130114031


[AppDelegate내에서 managedObjectContext객체 생성 및 준비과정]

NSManagedObject 관리객체

코어데이터로 저장하고 불러올수 있는 객체


NSManagedObjectContext 관리객체 컨텍스트

관리객체를 담는 그릇. 관리객체 컨텍스트에 관리객체가 포함되어 있음


NSPersistentStoreCoordinator 저장소 관리자

컨텍스트 내용을 파일이나 데이터베이스에 저장하는 객체


NSEntityDescription 엔티티

데이터 베이스의 테이블 구조


NSManagedObjectModel

엔티티를 모아놓은 공간


NSPropertyDescription

엔티티를 이루는 요소. 속성, 관계, 동적인 검색프로퍼티가 있다


NSFetchRequest

관리 객체 컨텍스트에서 객체를 검색할때 조건을 기술한 객체


NSPredicate

검색 요청에서 검색조건을 기술할때 사용


NSFetchedResultsController

테이블뷰가 사용하기 쉽게 검색 결과를 관리하는 도움 클래스 


코어데이터를 이용하는 일반적인 절차


1. 관리객체 컨텍스트 생성

2. 관리객체 컨텍스트에 관리객체를 검색

3. 관리객체 컨텍스트에 객체를 추가, 수정, 삭제

4. 실행 취소, 반복 처리

5. 저장소에 저장




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

[다른 클래스 내에서 사용과정]


AppDelegate 클래스에서 생성한 managedObjectContext를 다른 클래스에서도 사용하도록 하기 위해

AppDelegate객체에 접근하여 managedObjectContext를 사용하고자 한다.


어플리케이션에서 AppDelegate 클래스는 하나만 존재하기 때문에 싱글톤으로 관리할 변수가 있을 경우 다르게 구현해도 되지만,

AppDelegate에 프로퍼티로 설정하여 가져다 쓰면 된다.

다른 클래스에서 AppDelegate에 접근하고자 하고 싶을 때에는 다음과 같이 접근 가능하다

 [[UIApplication sharedApplication] delegate];


사용하고자 하는 클래스 내에 지정한 managedObjectContext의 객체를 초기화 할때

AppDelegate의 managedObjectContext 객체를 가져와 값으로 넣도록 한다.

 -(NSManagedObjectContext*)managedObjectContext {

if (managedObjectContext == nil) {

TestAppDelegate* delegate = (TestAppDelegate*)[[UIApplication sharedApplication] delegate];

managedObjectContext = [delegate managedObjectContext];

// if

return managedObjectContext;

// managedObjectContext


이제 CoreData를 사용하여 실제적으로 데이터를 가져오고 쓰고, 지우는 과정이다.

 /*read*/


NSError* error;

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc]init];

NSEntityDescription* dataEntity = [NSEntityDescription entityForName:@"CodeData" inManagedObjectContext:self.managedObjectContext];

[fetchRequest setEntity:dataEntity];

NSPredicate* predicate;


CodeData* codeData = nil;


predicate = [NSPredicate predicateWithFormat:@"code_type = %@ AND code_name = %@", codeType, codeName];

[fetchRequest setPredicate:predicate];

NSArray* data_list = [self.managedObjectContext executeFetchRequest:fetchRequest error:&error];

if([data_list count] > 0){

codeData = [data_list objectAtIndex:0];

}

[fetchRequest release];


 /*write*/


NSError* error;

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc]init];

NSEntityDescription* dataEntity = [NSEntityDescription entityForName:@"CodeData"

                                              inManagedObjectContext:self.managedObjectContext];

[fetchRequest setEntity:dataEntity];

NSPredicate* predicate;

CodeData* codeData = (CodeData*)[NSEntityDescription insertNewObjectForEntityForName:@"CodeData" 

                                              inManagedObjectContext:self.managedObjectContext];

codeData.code_type = codeType;

codeData.code_name = [codeDic objectForKey:DATA_VALUE];

codeData.code_desc = [codeDescDic objectForKey:DATA_VALUE];

[self.managedObjectContext insertObject:codeData]; //저장


[self.managedObjectContext save:&error]; //DB 커밋

[fetchRequest release];


 /*delete*/


NSError* error;

NSFetchRequest* fetchRequest = [[NSFetchRequest alloc]init];

NSEntityDescription* dataEntity = [NSEntityDescription entityForName:@"CodeData"

                                              inManagedObjectContext:self.managedObjectContext];

[fetchRequest setEntity:dataEntity];

CodeData* codeData = (CodeData*)[NSEntityDescription insertNewObjectForEntityForName:@"CodeData" 

                                              inManagedObjectContext:self.managedObjectContext];

codeData.code_type = codeType;

codeData.code_name = [codeDic objectForKey:DATA_VALUE];

codeData.code_desc = [codeDescDic objectForKey:DATA_VALUE];


[self.managedObjectContext deleteObject:tempData];//삭제


[self.managedObjectContext save:&error]; //DB 커밋

[fetchRequest release];

[출처] CoreData 사용(수정 중)|작성자 gigar


Posted by 삼스