Skip to main content

14 posts tagged with "iphone"

View All Tags

· 3 min read

이것저것 해본다고 작년에 Xcode 3.X 버전을 사용하다가 개발자들에게 4.0 beta가 release 되면서 사용했던 적이 있다. 두 개의 Xcode가 설치되고 나서도 특별히 문제는 없었는데, 이번에 Xcode 4.0.1 정식으로 나온걸 설치했더니 SDK 경로 인식에 문제가 생기는 것 같은 현상들이 보였다. 빌드가 잘 되는 프로젝트를 가져와 실행해 봤는데 iOS Simulator가 실행되면서 SDK를 찾지 못한다고 다시 설치해야할 가능성이 있을 수 있다는 내용의 경고메세지만 나타났는데 예전 버전의 Xcode와 동일한 경로에 설치되면서 뭔가 문제가 생기는 듯 했다. 그래서 선택한 방법은 이전 버전을 완전히 삭제하기. 방법은 아래와 같다.

터미널을 실행해서 다음의 command를 입력하면 끝.

 

sudo /Developer/Library/uninstall-devtools --mode=all

 

디렉토리까지 함께 삭제하기 때문에 터미널을 실행한 후 Xcode와 관련된 디렉토리로 이동했다면 다른 경로로 이동한 후에 삭제하는게 좋을 거라는 생각을 해본다.

(아마도 디렉토리를 못 지워서 삭제가 제대로 안될테니...)

 

별 오류 메세지 없이 삭제과정이 끝났다면 다시 Xcode 4.0.1을 설치한다. 나의 경우엔 설치도 문제없고, 실행, Running 모두 문제없이 해결.

 

OS X에서 설치된 응용프로그램의 삭제가 그냥 파일들을 휴지통에 버리는게 전부인데 사용자에게 별 다른 내용을 알려주지 않는다면 어느 경로에 설치가 되는지 dependency가 있는지의 여부를 알 방법이 없다. 이런 내용들을 잘 알려주거나(요건 보통의 Windows에서 설치 삭제할 때 종종 보게됨), 아예 새로운 버전의 Xcode 설치하려고 하면 사용자 선택에 의해 이전 버전을 깨끗하게 지우고 설치하게 하면 좋지 않을까? 란 생각을 해보는 중이다.

· 8 min read

검색기능을 추가할 일이 생겨서 여기저기 살펴봤으나 서점에 진열된 책들을 뒤져봐도 내가 필요한 부분을 찾지 못했다. 애플에서 제공하는 문서가 있기는 했으나 익숙하지 못한 나같은 초보에겐 그냥 문서에 불과했고 며칠간 고심하면서 인터넷을 뒤진 끝에 괜찮은 사이트를 발견. (아래 링크 참조) 복습도 할 겸 간단히 정리해본다.

1. 시작 / 준비

이 예제에서 설명에 사용하는 프로젝트는 기본적으로 Navigation based application으로 작성되어 개발자가 보여주고자 하는 어떤 리스트가 table view에 표시되는 application에서 시작한다. 목표로 하고 있는 완성 이후의 모습은 평상시엔 원본 데이터가 table view에 표시되고 화면 상단에 위치한 search bar에 찾고자 하는 문자열 입력 시 원본 데이터가 표시된 table view에 검색 결과가 나타나게 만드는 것이다. (아래 데모 동영상 참고) 그리고 원래의 navigation based application project의 Interface Builder에서 UISearchBar를 추가해 두어야 한다. 아래의 단계들을 거치고 나중에 UISearchBar의 Outlet과 delegate를 File's Owner로 설정하는 것 또한 기본적으로 해주어야 하는 작업.

demo

2. Header

원본 데이터를 저장하는 listOfItems와 검색된 결과를 저장할 copyListOfItems를 만들어야 한다. 실제 table view에 검색결과를 보여주고자 할 때는 copyListOfItems에 있는 내용을 보여주고 view를 reload 하면 끝. searchTableView는 실제 검색을 수행할 로직이 포함된 함수이고 doneSearching_Clicked는 검색 완료 후 누를 done 버튼 클릭시 호출될 함수이다.

3. searchBarTextDidBeginEditing

UISearchBarDelegate protocol에 포함되어 있는 함수로 UISearchBar에서 사용자가 어떤 문자열을 입력하려고 선택하면 자동으로 호출되는 함수이다. 이 함수가 자동으로 호출되기 위해서는 UISearchBar의 delegate가 File's Owner 혹은 다른 것으로 연결되어 있어야만 한다.

위의 코드에서 searching, letUserSelectRow 등은 상태를 알기 위해 사용할 flag 용도의 BOOL 멤버변수들이다. 많은 책에서 다루고 있는 예제들에서 종종 등장하는 navigationItem에 버튼 추가 루틴이 제일 하단에 포함되어 있다. Done 버튼을 추가하는 내용이고 Done 버튼 클릭시 doneSearching_Clicked 를 호출하라고 selector가 설정되어 있다.

3. textDidChange / searchBarSearchButtonClicked

UISearchBarDelegate protocol에 포함되어 있는 또 다른 함수 둘. 하나는 UISearchBar에 입력된 문자열이 변경되었을 경우 호출되는 함수이고 searchBarSearchButtonClicked는 UISearchBar가 focusing 되었을 경우 자동으로 나타나는 Soft keyboard의 Search 버튼을 클릭했을 때 호출되는 함수이다.

두 함수 호출시 모두 실제 검색작업을 수행해야만 한다. textDidChange에서는 사용자에 의해 입력된 문자열의 길이로 검색여부를 판단하고 (0일 경우에 검색하면 안되니까) searchBarSearchButtonClicked 에서는 단순히 검색 로직을 수행한다.

4. searchTableView

실제 검색 로직이 포함된 함수이다. 이 함수가 호출되었을 경우 검색결과 데이터를 저장할 copyListOfItems array의 내용을 업데이트 해주어야 한다.

위의 코드를 보면 rangeOfString이라는 함수를 사용하고 있는데 내용은 다음과 같다. 검색 대상이 되는 원본 데이터 listOfItems에 사용자가 입력한 searchText가 포함되어 있으면 그 문자열의 시작위치와 길이를 리턴하게 되는데, 길이가 0 이상이라는 얘기는 포함되거나 일치하는 부분이 있다는 의미이므로 그 문자열의 원본 데이터를 copyListOfItems에 추가한다.

5. numberOfRowsInSection

Table view를 사용하는 모든 프로젝트에 필수적으로 포함되어야 하는 함수로 몇개의 row를 보여줄거냐를 결정해 주어야 한다. 평상시에는 원본 데이터의 갯수를 표시해주면 되고, 검색시에는 검색된 데이터의 갯수를 리턴해야 한다.

6. cellForRowAtIndexPath

위의 row 갯수처럼 cell을 어떻게 표시할지도 결정해 주어야 한다. 위와 마찬가지로 평상시에는 원본 데이터의 문자열들을 표시해주면 되고, 검색시에는 copyListOfItems의 데이터들을 표시해주어야 함.

여기까지 해놓고 보면 기본적인 검색 기능은 제대로 동작을 한다. 내가 실제로 응용할 내용은 이것과는 조금 차이가 있지만 이런 기본적인 검색 기능을 살펴봐야 감이 와서...남은 오늘 하루는 어떻게 적용해야 하는지를 놓고 머리를 싸매야 한다. ㅠ

참조 사이트 : http://www.iphonesdkarticles.com/2009/01/uitableview-searching-table-view.html

· 11 min read

기상청에서 제공하고 있는 DB를 다룰 일이 생겼습니다. 상세 스펙 등이 기술된 문서를 살펴보니 꽤 종류도 많고 상세하더군요.(문제점도 있습니다.) 제가 담당하고 있는 부분이 xml로 넘어오게 될 몇 종류의 data들을 처리해야 하다보니 머리가 아픕니다. 분명 되어야 하는데 제대로 값을 만들어내질 못해서 고생 좀 했습니다.

1. XML Parsing 준비

예전에 교육받을 때 사용하던 것도 있고 iphone sdk가 제공하는 xml 처리 부분이 미흡하다는 이야기 때문에(물론 업데이트가 되면서 좋게 변했을지도 모릅니다. 확인은 아직....:)), TouchXML 이란 라이브러리(?)를 사용하기로 했습니다. 살펴보시면 알겠지만 C/C++로 작업할 때 이용하게 되는 보통의 xml parser와 사용법이나 생김새가 유사합니다. 설치나 기본 준비사항에 대해서는 아래의 블로그를 참고해 주세요. (처음 시작할 때 참고하기 좋게 잘 작성해 놓으신 것 같습니다.)

http://lambert.tistory.com/254

2. XML 형태

제가 사용해야 하는 여러가지 XML 중에 해결이 쉽지 않았던게 바로 아래와 같은 XML 입니다. Attribute와 Value가 혼합된 형태인데, 혼합된 형태라 해도 어차피 XML parsing 할 때 일반적인 절차처럼 root나 특정 node를 찾으면 거기서 attribute와 value를 각각 빼오면 되는거라 별 문제 없을거라 생각했는데 뭐가 문제인지 값을 얻어낼 수가 없더군요. 아래 그림을 들여다보면 current가 가장 상위에 있고 그 아래 weather, 그리고 각 지역별 기상정보가 담겨있는 local element 들로 구성되어 있음을 알 수 있습니다.

3. 시작

일단 xml 문서를 읽어들여야 합니다. 아래와 같은 코드를 작성하면 Document 덩어리는 문제없이 얻을 수 있습니다. 대략적으로 해석해보면, 우선 정해진 url에서 contents를 읽어 문자열로 저장하고, 공백과 줄바꿈을 제거해 순수하게 문자값들로 이루어진 문자열 data를 만들고 그 덩어리를 CXMLDocument로 만드는 내용입니다.

위의 내용대로 작성해서 run까지 해보면 별 문제는 없는데 로그에 이상한 부분이 보이더군요. Namespace가 이상한가 봅니다. 이 부분은 제가 건드릴 수 있는 부분이 아닌 것 같아서 그냥 넘어가긴 했는데, 이 때 부터 뭔가 찝찝해지기 시작했습니다.

4.  Xpath로 node 찾기

XML 표준을 따르고 있는 문서들을 다루다보면 xpath에 대해서 알아야 할 필요가 있습니다. 저도 업무상 필요했던 경우가 있어서 가볍게 익힌 적은 있는데 간단하게 얘기하면 xpath는 xml 문서 안에서 element의 위치를 찾기 위한 경로를 표준처럼 정해둔 형식을 의미합니다. 자세한 내용은 google 등을 이용하시면 엄청나게 많은 문서를 만날 수 있으니 생략하겠습니다.

(1) local 찾기

제가 가장 먼저 필요했던 내용이 local에 대한 data였기 때문에 local 부터 무작정 찾으라고 해봤습니다. 해보니 아무것도 나오지 않더군요. 이상했습니다.

NSArray *localArray = [xmlDoc nodesForXPath:@"//local" error:nil];

(2) weather, current 찾기

그래서 local 보다 상위에 있는 weather를 찾으라고 했는데 마찬가지여서 가장 상위에 있는 current를 넣어봤습니다. 재미있게도 결과는 같았습니다.

(3) 결론

root에서 각각의 element 이름으로 node 검색결과 아무것도 나오지 않기 때문에 //current/weather/local 형태를 이용해도 결과를 얻지 못할거라 추측할 수 있었는데, 실제로 해보니 추측했던대로 아무것도 나오지 않았습니다. 이상해서 혹시 입력한 xpath 형태가 잘못된거 아닐까 싶어 여기저기 찾아보면서 다른 경우에 대해서 해봤는데 뭐가 문제인지 알 수가 없더군요. Google weather API를 이용할 경우에는 문제가 없었습니다.

참고 : http://blog.naver.com/senjuny?Redirect=Log&logNo=150092832718

5. CXMLDocument의 rootElement로 접근하기

어쩔 수 없었습니다. 손쉬운 방법을 사용하고 싶었는데 허락하지 않으니 최악의 경우 문자열 만으로 해결하겠다는 각오를 하고 low level에서 접근하기로 했습니다. 그 중 첫번째로 사용한 방법이 element, node를 직접 얻어내서 child로 한단계씩 내려가는 방법이었습니다. TouchXML의 CXMLDocument class를 보면 rootElement라는 method가 있는데  prototype은 아래와 같습니다. 사실 이렇게 시작한 이유 중에 하나는 "root가 있기는 한거냐?" 라는 짜증 때문이었습니다.

- (CXMLElement *)rootElement;

method 이름처럼 root인 element를 return할 것이기 때문에 root element를 추출해 child의 child로 접근하는 방법을 생각했지요. rootElement를 추출한 상태에서 xpath를 적용해봤는데 결과는 예전과 동일했습니다. 참 이상해요. CXMLElement는 CXMLNode를 extends 하고 있기 때문에 CXMLNode의 method를 사용해 child에 접근을 시도할 수 있습니다.

- (CXMLNode *)childAtIndex:(NSUInteger)index;

여기까지 하면서 이상했던게 원래 기상청에서 얻어온 xml data의 weather node가 하나뿐임에도 CXMLNode의 childCount를 호출해보면 결과가 3이라는 사실이었습니다. 그래서 강제로(다른 method 들을 사용할만게 보이지 않아서) index 값을 조절해 weather node에 접근해서 childAtIndex를 childCount만큼 반복해 결과를 보니 local node 사이에 "text"가 존재한다는 걸 확인할 수 있었습니다. (element 사이에 무언가 존재하는 걸로 인식하고 있음. 아래 그림 참조)

6. 결론

Node 사이에 무언가가 있다고 인식하고 있기 때문에 원인은 정확하게 알 수 없지만 임시로 홀수 부분에 대해서만 처리하도록 작성하기로 했습니다. 하지만 계속 궁금합니다. 그리고 내가 뭔가를 빠뜨린게 아닐까 라는 의문도 생겼는데 혹시 아신다면 도움말 부탁드리겠습니다. 처음에 언급했던 것처럼 기상청의 다른 XML 들도 좀 살펴봤는데 부족한 점들이 몇가지 있습니다. 그 중 가장 아쉬웠던 부분이 XML 형태의 통일성인데, 제가 고생했던 XML은 attribute와 value를 함께 사용해 작성되어 있지만 어떤 XML은 정말 단순한 구조를 가지고 있었습니다. 똑같이 단순하거나 똑같이 복잡하면 그걸 이용해 개발하는 사람들의 입장에서는 작성하게 될 코드의 양도 줄고 재사용할 수 있는 가능성도 높아질텐데 그런 점이 좀 아쉽더군요. 그렇게 제공할 수 밖에 없었던 어떠한 이유가 있었는지도 모르겠지만요...참! 작성했던 코드는 아래와 같습니다. 그 아래엔 결과가...

· 6 min read

오랜만에 정리합니다. 계속 미루고 있다가 책을 마지막까지 다 보고 나서야 정리할 생각을 하게 되었습니다. 했던거 마무리는 해야 하니까요. 이번 chapter 7에서는 새로운 application을 만드는 것으로 시작을 합니다. 수배자에 대한 정보를 listing 해주는 application 인데, chapter 9 까지 이어지네요. chapter 7에서는 tab bar를 사용해서 수배자와 검거된 사람들의 list를 분리하고 그에 필요한 data 관리는 Core Data로 하는 방법을 알려주고 있습니다. Core Data는 다시 별도로 정리를 해야할 것 같습니다. 꽤 복잡해 보여서요. 물론 직접 DB 관리하면서 쿼리문을 코드에 넣는 것보다는 훨씬 낫겠지만요.

Tab bar icon 추가 혹은 변경

Tab bar에 있는 물음표를 선택하던가 직접 구성요소를 다 펼쳐서 Tab bar item을 선택합니다. 그 이후에 오른쪽의 속성 창에서 Identifier 부분을 Custom으로 변경하고 Image에 넣고자 하는 혹은 바꾸고자 하는 image 파일을 선택해주면 됩니다.

위의 그림처럼 설정하면 끝. 간단합니다. :)

Core Data에서 Entity의 attributes 옵션들

Entity를 새로 구성하려고 attribute 들을 추가하고 수정하다보면 몇 개의 옵션들이 있는 것을 볼 수 있습니다. 그 중에서 Transient는 data를 따로 저장할 필요가 없는 임시 attribute 라는 걸 알려주는 용도이며, Indexed는 Core Data가 index 값을 별도로 만들어 빠른 검색을 가능하게 만들 필요가 있을 때 사용합니다.

Core Data의 세가지 요소

Core Data는 세가지 요소로 구성된다고 책에 언급되어 있습니다. 개인적으로는 entity도 하나의 요소가 되는거 아닌가 싶은데 일단 책에서는 그렇게 말하고 있네요. 세가지는 아래와 같습니다.

Managed Object Context : Data를 관리하는 녀석. Entity로 만들어낸 data model 들도 context가 관리합니다.

Persistent Store Coordinator : 아래 store를 관리하는 역할을 수행합니다.

Persistent Object Store : 용어 그대로 store 입니다. 책의 예제에서는 sqlite 파일이 되겠습니다.

NSFetchRequest

Managed Object Context에게 찾고자 하는 객체를 알려주기 위한 용도로 NSFetchRequest 라는 클래스를 사용해야 합니다. NSFetchRequest를 사용할 때 크게 세가지를 결정해줘야 하는데, Entity Info, Predicate, Sort Descriptor 가 그 것입니다. 어떤 entity를 찾을건지 결정해주고(Entity Info), 충족해야 하는 조건 혹은 검색 조건을 만들어줘야 하구요(Predicate). 조건을 충족하는 data가 있을 경우에 정렬시켜서 뽑을 수 있습니다.(Sort Descriptor)

NSSearchPathForDirectoriesInDomains

책에서 계속 simulator를 사용하고 있기 때문에 실제 device와 약간 차이가 나는 부분들이 있습니다. 그 중에 하나가 DB가 존재하는 위치인데, device와 달리 simulator 사용시에는 다른 디렉토리에 저장이 되기 때문에 별도의 method를 사용해서 DB file을 찾아줘야만 합니다. 그 때 사용하는 method가 NSSearchPathForDirectoriesInDomains 입니다. 이 method의 prototype을 살펴보니 지금까지 보아왔던 Objective C의 method와는 많이 다르네요. C, C++, Java 등에서 흔히 사용하는 method와 같은 모습입니다. (아래의 그림, NSSearchPathForDirectoriesInDomains prototype)

NSSearchPathForDirectoriesInDomains method의 문서를 살펴보니 특정 directory, domain을 위한 경로 문자열을 리스트로 넘겨준다고 되어있습니다. NSSearchPathDirectory와 NSSearchPathDomainMask는 각각 enum으로 여러가지 상수들을 포함하고 있습니다. Chapter 7의 예제에서는 각각 NSDocumentDirectory, NSUserDomainMask를 사용했구요.

NSManagedObject의 생성

예제에서는 NSManagedObject를 context에서 가져오는 것으로 설명되어 있습니다만, 별도로 생성하는 방법도 있다고 합니다. 일반적인 클래스 객체의 생성처럼 alloc, init만 해서 생성하는 것은 안되고 아래처럼 entity에 대한 정보도 필요하다고 되어있네요.

[NSEntityDescription insertNewObjectForEntityForName:@"Fugitive"

inManagedObjectContext:managedObjectContext];

· 4 min read

Chapter 8에서도 계속 같은 내용의 application을 개선하고 업데이트 하는 내용입니다. 주로 다루고 있는 내용은 data model에 변경사항이 생겼을 때 Core Data를 이용해서 쉽고 빠르게 처리할 수 있는 방법이구요. 수배자 명단에서 수배자를 검거했다고 선택하게 되면 검거자 리스트에도 해당 data가 표시되는 것까지 처리하고 있네요. 이번 chapter까지 끝내면 심심하지만 원래 의도했던 기본 기능은 갖출 수 있습니다.

Data Model의 변경

기존의 data model을 사용하다가 속성을 추가, 삭제, 변경하고 싶은 경우가 분명히 발생할 것 같습니다. 보통의 경우라면 DB 자체를 다루게 될테니 DB 스키마가 변경됨을 의미하고, 귀찮고 복잡한 작업을 하나하나 해줘야 합니다. 하지만 Data Core를 이용하면 작업이 훨씬 줄어들 수 있습니다. 잘 생각해보면 처음에 Data Core를 사용하기 시작할 때 부터 DB와 관련된 복잡한 작업은 하지 않았었는데, 확실히 이 부분은 편리하다는 생각이 드네요. 아무튼 Data Model을 변경할 일이 생기면 아래의 작업들을 순서대로 해주면 됩니다.

1. 변경할 기존의 data model 선택

2. Design > Data Model > Add Model Version 메뉴 선택

3. 2.과정을 거쳐서 생성된 새로운 버전의 data model 선택

4. Design > Data Model > Set Current Version 메뉴 선택

5. Data Model 변경

Managed Object Context에 data 저장 그리고 취소

data를 저장하기 위해서는 save (commit 한다는 것을 의미), 되돌리고 싶다면 rollback message를 보내라고 되어있습니다. 참 직관적이라는 생각이 드네요. save, rollback, ...:)

viewWillAppear / viewDidLoad

지금까지 참 많이 보게 되었던 method 들입니다. 이외에도 몇가지가 더 있지요. View를 표시하는 method들 여러개를 한꺼번에 정리할 필요가 있어 보입니다. 일단 viewWillAppear와 viewDidLoad를 보면 method 이름이 다른 것처럼 의미와 용도에도 명확한 차이가 있습니다. viewWillAppear는 해당 view가 화면에 표시될 때 마다 매번 호출되는 method 입니다. 예를 들어 navigation control을 사용한 application에서 item 한가지를 선택해서 상세 view가 표시되는 경우 viewWillAppear가 호출되고, 원래 view로 되돌아가서 다시 상세 view가 표시되면 다시 호출이 됩니다. 그러므로 매번 호출될 필요가 있는 구문들만 viewWillAppear에 넣어주어야 할 것 같습니다. 반면에 viewDidLoad는 전체 view (xib file)가 메모리에 올라올 때 한 번만 호출되는 method 입니다. 정확히는 load된 직후에 호출되겠네요. 이름에 did가 있으니까요.

· 5 min read

Chapter 9가 마지막입니다. 몇 주 전부터 느끼던 건데, 제가 보고 있었던 Head First iPhone Development 라는 책이 초심자들이 무작정 따라하면서 흥미를 느끼기에는 괜찮긴 한데 Head First 시리즈가 가지고 있는 특성 (여기저기 낙서하듯이 흐트러져 있는) 때문에 종종 복잡하고 혼란스럽게 느껴질 때가 있었습니다. 또 진도가 나갈수록 새로운 control의 사용법들이 등장했는데, 이런 내용은 어차피 나중에도 참조해야 하는 거라서 chapter 7 쯤 되니까 직접 부딪히면서 배우는게 낫겠다는 생각이 들더군요. 어쨌든 마지막 chapter에서는 고급과정인 카메라와 맵킷을 사용하는 방법이 등장합니다. 시뮬레이터 기반으로 구현했기 때문에 실제로 카메라를 직접 동작시킬 수는 없었지만 Map Kit과 Core Location의 활용은 꽤 흥미로웠습니다.

Flipping

여러 application을 사용해보다 보면 화면전환시의 animation 중에 화면이 뒤집히면서 변경될 때가 있습니다. 이게 flipping 입니다. 이것을 구현하는 방법은 매우 간단합니다. 전환하는 스타일을 flip 하겠다는걸 의미하는 UIModalTransitionStyleFlipHorizontal 상수만 사용하면 되더군요. 물론 몇가지 작업도 해야하지만 보통의 view를 사용할 때와 같습니다.

위의 사용예문을 보면, 우선 CapturedPhotoViewController.xib 파일로 CapturedPhotoViewController의 객체를 생성했습니다. 그리고 생성한 controller의 modalTransitionStyle을 UIModalTransitionStyleFlipHorizontal로 지정해주고 controller를 modal view로 설정해주는 걸로 끝이네요. 간단합니다. 정말. 참 showInfoButtonPressed 는 화면 전환을 명령할 수 있는 button 등을 미리 만들어두고 IBOutlet으로 만들어서 연결되어 있어야 호출이 가능하겠죠.

UIImagePickerController

카메라를 사용하기 위해서는 UIImagePickerController를 이용하면 된다고 합니다. UIImagePickerController를 사용하게 되면 일반 사용자들은 카메라로 사진을 찍거나 라이브러리에서 사진을 선택해서 가져올 수 있게 됩니다. Controller를 생성한 후에 sourceType이라는 속성에 어떤 값을 지정하는냐에 따라 기능을 선택할 수 있는데, 속성값으로는 UIImagePickerControllerSourceTypePhotoLibrary, UIImagePickerControllerSourceTypeCamera, UIImagePickerControllerSourceTypeSavedPhotosAlbum 세가지가 있습니다. 각각은 photo library, camera, photo album에서 이미지를 가져오겠다는 것을 의미하며 복수 선택도 가능합니다. 일반적인 경우처럼 "|(OR)" 를 쓰면 되겠죠. 또 현재 application이 동작중인 device에서 어떤 것을 지원하고 있는지 알고 싶다면, isSourceTypeAvailable 이라는 method를 써서 분기하면 되겠습니다.

AR (Augmented Reality)

최근 유행하고 있는 증강현실을 구현해보고 싶다면 어떻게 해야 하는지 궁금했습니다. 뭔가 복잡할 것 같기도 했고, 구석구석 따져보면 간단할 것 같기도 했는데 카메라로 비치는 실사 자체가 호기심을 자극하기 때문에 그런 혼란을 갖게 되는지도 모르겠습니다. 책에서는 아주 짧게 언급되어 있는데 UIImagePickerController 위에 overlay view를 올리면 된다고 하네요. 이 부분은 관심이 많기 때문에 나중에 자세히 확인해보고 정리할 생각입니다.

· 9 min read

제 생각엔 가장 길었던 chapter 였던 것 같습니다. 몸이 안좋은 것도 있었는데 토할 정도로 짜증도 좀 나고 그랬죠. 이번에도 어김없이 DrinkMixer를 예제로 사용합니다. 흐름을 보면 소프트 키보드의 등장으로 인해 짤리는 UI, 그리고 그 때문에 컨트롤이 불가능한 것들을 어떻게 처리할 것이냐가 한가지 입니다. 책에서는 예전에도 등장했던 키보드와 관련된 이벤트를 받아서 그 때 마다의 정확한 화면 사이즈(키보드가 포함되지 않은 영역)를 계산하고 새로 만들게 되는 스크롤 뷰에 값을 넘겨 화면이 스크롤 되도록 처리하고 있습니다. 어떻게 생각해보면 키보드가 나타났다 사라지는 것, 그에 따라 화면이 가려지고 스크롤이 되지 않으면 컨트롤이 되지 않는게 당연한데 이런걸 자동으로 처리할 수 있게 제공하면 어땠을까 싶습니다. 사실 당연한 것들을 처리하는 것도 우리에겐 짐이니까요. :) 또 하나 다루는 내용은 이미 만들어져 있는 테이블 뷰에 보여지는 데이터들을 편집하고 삭제하는 기능을 추가하는 방법입니다. SDK에서 제공되는걸 그대로 사용하기 때문에 이 부분은 상당히 편하게 관리할 수 있다고 생각됩니다. 아래는 메모하고 싶었던, 이번 chapter에서 새로 등장한 녀석들입니다.

NSNotificationCenter

예전에도 나왔던 내용입니다만, 이벤트 등록을 위해 사용하는 클래스입니다. 책에서는 아래처럼 사용하고 있네요.

[[NSNotificationCenter defaultCenter] addObserver : self selector : @selector(keyboardDidShow:)
name : UIKeyboardDidShowNotification object : nil];

위의 예문을 잠깐 해석해 보겠습니다. NSNotificationCenter의 defaultCenter를 사용할건데 이벤트가 발생했을 때 전달받을 객체(Observer)는 자기 자신으로 설정하고, 이벤트가 왔을 때 호출할 녀석은 keyboardDidShow라는 method 입니다. 그리고 모든 이벤트가 발생했을 때 알려주는게 아니고, UIKeyboardDidShowNotification 이라는 녀석만 관리할 겁니다. 마지막 어떤 객체가 이벤트를 발생시켰는지는 신경쓰고 싶지 않습니다. (object : nil 부분) 이벤트를 처리하는 부분에 대해서 다시 정리한 이유는 아래 selector 때문입니다. 책에서는 아래처럼 정의하고 있습니다.

- (void)keyboardDidShow : (NSNotification *)notif  {
...
...
}

Selector라고 불리우는 것은 C/C++에서 콜백함수와 유사하다고 판단되는데 method 자체는 개발자가 입맛대로 만들면 되는 녀석입니다. SDK에서 제공하는게 아니라는 얘기입니다. Parameter로 NSNotification의 포인터 타입의 notif란 이름으로 받으려고 책의 저자는 생각했나봅니다. 이 parameter는 알아서 전달받게 되겠죠? NSNotification은 userInfo 라는 field를 가지고 있는데 여기에 이벤트에 대한 자세한 정보들이 담겨있어서 책에서는 이 정보들을 이용하게 됩니다.

갑자기 등장한 여러가지 구조체들

소제목처럼 정말 갑자기 여러가지 구조체들이 등장합니다. 전 뭔지도 모르겠는데 갑자기 등장하니까 짜증이 장마처럼 밀려오더군요. 그래서 짜증을 풀고자 xcode에서 document를 불러 내용을 확인해 봤더랬습니다. :)

CGSize

정의 부분을 보면 정말 간단한 구조체입니다. CGFloat type의 width, height 꼴랑 두 개만 멤버로 보유하고 있습니다. CGFloat은 안봐도 float과 관련이 있겠다는 생각이 들어서 확인해보니 역시 아래처럼 정의되어 있네요.

#if defined(__LP64__) && __LP64__
typedef double CGFloat;
#define CGFLOAT_MIN DBL_MIN
#define CGFLOAT_MAX DBL_MAX
#define CGFLOAT_IS_DOUBLE 1
#else /* !defined(__LP64__) || !__LP64__ */
typedef float CGFloat;

LP64가 뭔지는 모르겠지만 아마 CPU data 처리량에 관련된게 아닐까 싶습니다.(64bit) 경우에 따라서 CGFloat은 double형이 되거나 아니면 반쪽짜리 float이 되겠네요.

CGRectValue

이건 구조체가 아니고 method 입니다만 CGRect 구조체를 리턴해주는 녀석입니다.

CGRect

이 녀석은 rectangle의 위치와 dimension 값을 포함하는 녀석이라고 문서에 되어있는데, CGPoint type의 origin과 CGSize type의 size를 멤버로 두고 있네요. origin은 위치일테고 size는 width와 height를 가지고 있는 구조체니 dimension을 의미합니다.

NSSortDescriptor

배열에 있는 data를 정렬할 때 사용하는 클래스입니다. 이 클래스의 객체가 직접 정렬된 data를 보유하는 것은 아니고 이름처럼 정렬을 어떻게 하겠다는 설정값들만 가지고 있는 descriptor 입니다. 예제에서는 아래와 같이 사용하고 있는데요.

NSSortDescriptor *nameSorter = [[NSSortDescriptor alloc] initWithKey : NAME_KEY ascending : YES
selector : @selector(caseInsensitiveCompare:)];
[drinkArray sortUsingDescriptors : [NSArray arrayWithObject : nameSorter]];
[nameSorter release];

NSSortDescriptor 객체를 새로 할당받을 때 NAME_KEY 를 이용해 초기화 하고 올림차순으로 정렬하겠다고 작성되어 있습니다. Selector는 비교방법을 어떻게 할건지 추가 정보를 제공합니다. 예문에서는 대소문자를 구별하지 않고 비교하라고 되어있는거구요. 이렇게 만들어진 descriptor를 가지고 NSArray를 만들고 이를 이용해 실제 drinkArray의 data들을 정렬합니다. 물론 다 사용했으니 release 해줘야겠죠.

이렇게 chapter 6까지 진행하고 보니 몇가지 컨트롤을 다루는 방식에 대해서는 어느정도 이해가 됩니다. 그런데 사실 이 모든걸 다 기억하고 있기란 거의 불가능합니다. 왜냐하면 아직 iphone에 익숙하지 않은 초보거든요. 어차피 어떤 application을 작성하려고 할 때 문서들과 sample 들을 참조해 가면서 작성해야 할텐데, 이런 생각을 하다보면 슬슬 지치네요. 그래도 시작을 했으면 끝을 맺어야 새로운 의욕이 생기게 마련이니 오버스럽게라도 후딱 끝내야겠습니다.

· 6 min read

Chapter 5는 chapter 4에서 크래쉬된 application을 디버깅해서 오류를 수정하는 내용과 사용성 측면의 보완, 그리고 모달뷰를 작성하고 칵테일 추가기능을 넣게 되는데 뷰를 상속해서 사용하는 방법 등을 다루고 있습니다. 다루는 내용도 많고 기네요. :)

viewWillAppear

View가 나타나는 시점에 대한 method 들을 여럿 볼 수 있습니다. viewDidLoad / viewDidUnload 도 있었고 viewWillAppear, viewWillDisappear 등도 있는데 이름만 보면 대충 view가 나타나는 시점에 따라 여러개의 메소드가 제공됨을 추측할 수 있습니다. viewWillAppear는 실제로 view가 추가되기 전에 호출되는 것으로 문서에는 명시되어 있네요. parameter로 BOOL type의 animated를 받고 있는데, YES를 넣으면 view가 나타날 때 animation 효과를 줄 수 있습니다.

문자열 상수의 이용

이전 chapter에 이미 설명되어 있는 내용입니다. 리팩토링을 하자면서 뜬금없이 코드 안에서 사용되는 문자열 들을 별도로 뽑아서 헤더파일 안에 상수로 정의해 두고 문자열 상수로 대체하는 부분이 있었는데요. 이번 chapter에서도 고스란히 그 상수들을 이용하고 있습니다. 여러번 사용할 문자열들을 간단하게 재사용 할 수 있다는 측면에서도 리팩토링의 효과를 충분히 얻을 수 있는 예입니다. 하지만 더 중요한 부분은 단순히 문자열을 개발자가 입력해 사용할 경우 오자가 있어도 컴파일 결과는 정상일 가능성이 높기 때문에 나중에 문제가 생겨도 디버깅하기가 상당히 까다로워질 수 있는데 반해 문자열 상수를 이용할 경우 문자열 상수를 잘못 사용할 경우 컴파일러가 미리 잡아주기 때문에 디버깅과 개발에 들어가는 시간과 노력을 조금이나마 줄일 수 있습니다.

Disclosure Indicator

사용성에 대한 문제가 언급되면서 HIG (Human Interface Guidelines) 라는 용어가 등장합니다. 애플에서 UI의 일관성과 application의 사용성을 높이기 위해 작성해 둔 규칙 같은건데, 꼭 지키라고 강조하고 있네요. 이런 애플의 강한 규제들을 싫어하는 분들도 있지만 저는 어느정도 찬성입니다. 개발자가 궁극적으로 원하는건 자신의 산출물을 통해 사용자들이 멋진 경험을 하게 만드는 것이라고 생각하는데 개발자가 자유도를 조금 빼앗기더라도 사용자에게 일관된, 그리고 멋진 경험을 선사할 가능성을 조금 더 높일 수 있는 방법이라고 생각하기 때문입니다. 그래서 이번 장에서 칵테일 리스트를 누르면 어떤 내용이 더 나타날 것이라는 예상을 할 수 있도록 disclosure indicator 라는 것을 사용합니다. 보통의 리스트가 있는 iphone application을 보면 오른쪽 끝에 화살표가 삽입된 경우가 있는데 이것이 disclosure indicator 입니다. 이것 이외에도 몇가지 type을 더 제공하는 듯 한데, 언젠가는 다른 type 들도 사용할 기회가 있을 겁니다. 이것을 넣고 싶으면 단순히 리스트의 cell의 accessoryType에 사용하고 싶은 타입의 indicator 상수를 넣어주면 됩니다. 이렇게요. 매우 간단합니다. :)

cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;

Selector

Chapter 5에서 Save와 Cancel 버튼을 만들게 됩니다. 이 때 버튼을 초기화 하면서 action이라는 이름의 파라미터를 넣게 되어있는데 @selector(save:) 이런 식으로 입력을 하고 있습니다. @ 마크는 NSString type 이란걸 알려주기 위해서만 사용한다고 생각했는데, 참 혼란스럽습니다. 찾아보니 @selector 자체는 SEL type을 가리키기 위한 키워드 정도로 그냥 생각하면 될 듯 합니다. C, C++에서 함수 포인터를 이용해 특정한 상황에서 해당 함수를 호출하는 방식과 유사하다고 볼 수 있습니다. 주의해야 할 사항은 method의 콜론까지 포함해서 입력해주어야 한다는 사실입니다. Selector에 대해서는 그냥 감만 잡고 나중에 또 등장하면 자세히 살펴봐야겠습니다.

· 5 min read

Ch.4 부터는 독특한 Objective C 문법이 나오질 않네요. 객체에 메세지 보내는 방법, property, protocol 등만 익숙하면 크게 어려울게 없는 듯 해서 중요해 보이는 부분이나 이상했던 부분 위주로 정리해볼까 합니다.

UITableViewController

이전 chapter 들에서 picker 라는게 나오면서 DataSource와 Delegate를 protocol로 지정해 사용했었는데, Chapter 4에 사용되는 UITableViewController는 DataSource와 Delegate를 이미 protocol로 사용하고 있기 때문에 개발자가 별도로 선언할 필요가 없다고 합니다. 실제로 UITableViewController의 정의 부분을 찾아보니 아래처럼 protocol로 선언되어 있네요.

그래서인지 이미 UITableViewController가 구현해야만 하는 method 들이 이미 만들어져 있습니다.

NSMutableArray

NSMutableArray를 사용하는 부분이 있는데 초기화를 개발자가 원하는 문자열을 사용해 초기화 하는 것으로 예제들이 되어 있습니다. 물론 다른 방법(method)을 사용해도 무방하겠지만요. 재밌다고 생각한건 초기화 할 때 일련의 문자열 마지막에 nil이 들어있다는 사실입니다. 이전 chapter 들에서도 비슷한 부분들이 있었는데, C / C++ 과 비슷하게 문자열 마지막의 nil은 문자열의 마지막임을 알려주는 용도로 사용된다고 합니다. 물론 배열 안에 추가되는 것은 아니구요.

NSBundle

이번 chapter에서 독특한 클래스가 등장합니다. NSBundle 이라는. Property list를 DB 대신 이용하면서 property list의 경로를 찾기 위해 사용하고 있는데, 문서들을 찾아보니 프로그램에서 사용되는 resource나 code 들을 그룹핑한 파일시스템에서의 위치를 표현한다고 하네요. Cocoa에서 몇개의 파일들과 디렉토리들을 묶음으로 관리하는데 그 묶음을 bundle이라고 한다고 합니다. 자세한 내용은 더 찾아봐야 하겠지만 일단은 우리가 작성하는 application에 존재하는 파일이나 resource 들을 읽어올 때 사용해야 한다고 생각하면 편할 듯 합니다. 나중엔 음원이나 영상들을 얻어와서 읽어들일 때도 사용가능할 듯 싶네요.

UIViewController subclass 생성시 유의사항

이번에 사용되는 예제에서 detail view를 위해 UIViewController subclass를 생성해서 작성하게 됩니다. 이 때 책에 표시가 되어있는데 저는 깜빡하고 넘어갔던 부분이 "with XIB for user interface" 옵션인데요. 이걸 체크하지 않으면 h와 m 파일만 생성됩니다. 전 xib 파일을 따로 생성해서 작업을 했었는데, 어찌된 일인지 나중에 run 해보면 빌드는 정상인데 run time error가 발생하더군요. xib 파일을 따로 인식하지 못하는 모양이었습니다. 분명 별도로 생성한 xib 파일을 나중에 엮어주는 방법이 있을텐데 한참이나 고생하다가 옵션 체크해서 새로 작성했던 기억이 있습니다. 저처럼 고생하는 일이 없기를... 그래도 이런 실수들 한거 전 좋습니다. 알아낸게 있으니까요. :)

Debugger와 Console 단축키

단축키는 여러모로 편리합니다. 머릿속에 각인시켜 두는게 힘들긴 하지만요. 이번 chapter에서 debugging에 대한 맛보기가 등장하는데 따로 debugger 윈도우나 console이 올라오지 않으면 아래의 단축키를 사용하세요.

Debugger : Command + Shift + Y

Console : Command + Shift + R

· 7 min read

이번주에 연구회에서 책 Ch.3에 대한 내용을 발표하게 되어서 Ch.2 부터 정리했습니다. 원래 지난주에 Ch.3까지 다 정리하긴 했는데 그게 머릿속으로만 정리한거라 ㅎㅎ. Ch.2와 3이 거의 연결되는 내용이라 따로 분리할 필요는 없을거 같긴 한데 그래도...

Protocol

@interface InstaTwitViewController : UIViewController
<UIPickerViewDataSource, UIPickerViewDelegate> {
...
}

위의 예문에서 보면 특이하게 < > 기호로 감싼 부분이 있는데, UIPickerViewDataSource와 UIPickerViewDelegate 라는 protocol을 사용하겠다는 의미입니다. Protocol이란 개념이 Objective C 2.0 부터 포함되어 있는거 같은데, 다른 언어들만 접해본 입장에서는 생소한 개념입니다. 근데 뭐 간단히 생각해보면 일반적인 protocol을 보통은 '어떤 시스템에서 미리 약속해 놓은 규약' 정도의 의미로 사용하고 있으니 비슷하게 이해하고 넘어가면 되지 않을까 싶네요. Objective C의 특징과 연결해서 생각해보면 Objective C가 다중상속을 금지하고 있기 때문에 보완하고자 만들어진 개념이 아닐까 생각됩니다. 전 Java에서 Interface와 유사한 녀석이라 생각하기로 했습니다.

alloc & init

activities = [[NSArray alloc] initWithObjects : @"abc", @"def", nil];

Objective C에서는 new 대신 alloc이란 method (message)로 새로운 객체 생성을 하는 듯 합니다. 보통 class 들의 멤버들을 열어보면 보통 초기화 method들이 제공되는 듯 한데 초기화도 방법에 따라 다양한 녀석들이 있습니다. 위의 경우엔 (initWithObjects) 다른 객체들로 초기화하는 내용을 표현하고 있습니다. 위에서는 NSString type의 객체들이네요. 그리고 마지막은 nil (=null)을 넣어두고 있습니다. Objective C에서 제공하는 method 들은 대부분 이름만 봐도 대충 의미가 파악된다는게 참 좋은 점이라고 생각합니다. 근데 쓰면서 보니 마지막엔 꼭 nil을 넣어주어야 하는 것인지 갑자기 궁금해지네요. 이 내용은 확인해서 추가해야겠어요. 참 alloc으로 새로 할당한 녀석들은 꼭 나중에 release 해야 합니다.

참으로 헷갈렸던 method 이름

- (NSInteger)pickerView : (UIPickerView *)pickerView numberOfRowsInComponent : (NSInteger)component {
....
}

이거 전체가 method의 prototype입니다. 참 이상하고 헷갈리고, 책을 당장이라도 덮고 싶게 만드는 것들 중 하나입니다. 근데 한 번 이해하니 오히려 편한 부분도 있습니다. 그냥 보면 굳이 parameter로 무엇을 전달해야 하는지 reference를 찾아볼 필요가 없어요. :) 그래서 제 나름대로 이해하기 쉽게 배열하기로 했습니다. 그럼 더 편하거든요. 위의 method의 정확한 이름은 이렇게 표현할 수 있습니다.

(NSInteger)pickerView : numberOfRowsInComponent

pickerView만 이름인게 아니라 numberOfRowsInComponent까지 이름이 되는거죠. 이렇게 이해하면 됩니다. 원래 pickerView라는 녀석인데 numberOfRowsInComponent에 필요한 parameter를 하나 더 받을거다 라고요. 그래서 나름 쉽게 풀어서 다시 쓰면 아래와 같습니다.

(NSInteger)pickerView : (UIPickerView *)pickerView
numberOfRowsInComponent : (NSInteger)component {
....
}

다시 쉽게 해석해보면 첫번째 parameter로 UIPickerView의 pointer type인 pickerView를 가져야 하고, numberOfRowsInComponent라는 parameter가 필요한데, NSInteger type의 component란 이름으로 parameter를 받을거란 얘기입니다. 책에선 외부 parameter, 내부 parameter란 이름으로 얘기하고 있는데 공식적으론 numberOfRowsInComponent란 parameter가 필요하고 실제로 component란 이름의 parameter를 넘긴다고 이해하면 됩니다. 그러므로 method 내부에선 component를 가지고 처리하면 되겠습니다. numberOfRowsInComponent를 보면서 가만히 생각해보면 이게 어떤 component 안에 있는 row의 갯수를 어떻게 하겠다는 얘기니 필요한 component를 parameter로 받겠다는 걸 알 수 있습니다. 특이하게 보통 이런 경우 number of rows를 return하게 되더군요. 실제로 이 method는 특정 component를 parameter로 받아서 그 component의 row 갯수를 NSInteger type으로 return하도록 작성되어 있습니다. 설명이 좀 복잡해진 듯 하지만, 3개 이상의 parameter 들도 이런 식으로 작성되기 때문에 이런 형태에 대해 익숙해져야 할 듯 합니다.

3개의 parameter를 사용하는 예

- (NSString *)pickerView : (UIPickerView *)pickerView
titleForRow : (NSInteger) row
forComponent : (NSInteger)component {
....
}

위에서 설명한 것과 비슷한 방법으로 해석해보면 제일 끝에서부터, component를 받을거고 row도 필요하고 NSString * type의 값을 return 할거라는 걸 알 수 있고, 더 나아가면 특정 component를 받아서 component 별로 row를 입력받을 생각이고 결국은 해당 row의 title을 return 하겠다는 걸 추측해볼 수 있습니다.