새로운 버전의 Xcode가 Release 되었을 때 해야할 일

이것저것 해본다고 작년에 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 설치하려고 하면 사용자 선택에 의해 이전 버전을 깨끗하게 지우고 설치하게 하면 좋지 않을까? 란 생각을 해보는 중이다.

 

 

UISearchBar를 이용한 검색

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



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



도시별 현재 날씨 XML Parsing 하기 (TouchXML)

기상청에서 제공하고 있는 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은 정말 단순한 구조를 가지고 있었습니다. 똑같이 단순하거나 똑같이 복잡하면 그걸 이용해 개발하는 사람들의 입장에서는 작성하게 될 코드의 양도 줄고 재사용할 수 있는 가능성도 높아질텐데 그런 점이 좀 아쉽더군요. 그렇게 제공할 수 밖에 없었던 어떠한 이유가 있었는지도 모르겠지만요…참! 작성했던 코드는 아래와 같습니다. 그 아래엔 결과가…



이해를 돕기 위한 Objective-C 문법 (Ch.9)

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를 올리면 된다고 하네요. 이 부분은 관심이 많기 때문에 나중에 자세히 확인해보고 정리할 생각입니다.


이해를 돕기 위한 Objective-C 문법 (Ch.8)

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가 있으니까요.