최초 작성일 : 2010/10/04 01:25
1. 드디어 실제 소스 분석!
앞서의 글들은 소스 분석이라기 보다는 앱 개발 작업 전반에 걸쳐 필요한 사항들을 일반적인
입장에서 적어본 글들이었습니다. 물론 모든 내용이 적절하게 기술되었는지에 대해서는
확신이 서질 않지만 어쨌든 글을 작성하기에는 이런 일반론적인 내용이나 세부적인 기술이
좀 더 편하긴 한 것 같습니다. 적절한 참고 자료를 선택해 인용하고 부연하는 정도로
충분하니까요…^^
하지만 이제부터 작성되는 글 내용은 제가 실제로 개발을 했던 소스를 살펴보는 것이기 때문에
사실상 꽤나 부담이 됩니다.
자바 경력은 어느 정도 있지만 Objective-C라는 생소한 언어와 iOS라는 생소한 플랫폼으로
작업을 하는 것이다보니 효율적인 코드라든지 메모리 관리, 차후의 유지보수에 관한 측면 등은
미처 생각을 할 여유조차 없이 그저 기능 구현에 매달리게 되었고 결국 이런 소스를 남들에게
공객하고 또 감히 그 것으로 뭔가를 설명한다는 것은 저의 (개발자로서의) 모든 치부가 고스란히
드러나는 일이 될 수도 있기 때문이죠…^^;;;
하지만 글을 진행하는 동안 종종 말씀 드렸듯이 제 글은 참고서로써 혹은 따라야 할 모범으로써가
아니라 그저 타산지석으로, 다시말해 '이렇게 개발하는 사람도 있구나'. 혹은
'이사람은 이런 부분을 잘못했구나'라는 부분들을 함께 공유하고 또 그 것을 통해 저도
배우겠다는 목적으로 글을 쓰게 된 것입니다.
사족은 걷어치우고 본론으로 들어가서 7회째의 이야기를 시작해 보도록 하겠습니다.
이번 글에서는 제가 개발한 아이폰 앱인 iPhotoDiary의 메인 화면 작업 과정에 대해 살펴보도록
하겠습니다.
소스 내용 중 CoreData 사용 부분에 대해서는 이전 글들에서 모두 설명을 드렸기 때문에
CoreData에 대해 궁금하신 분들은 저의 이전 글들 중 3~5편을 살펴 보시기 바랍니다.
아울러 다시 한 번 양해 말씀드리지만 제가 머리털 나고 처음으로 접해본 언어를 가지고
처음으로 접해본 플랫폼에서 처음으로 만든 애플리케이션입니다. 이점 감안해주시길
부탁드립니다. 아울러 제 글이 어떤 형태로든 도움이 되었다고 생각되시는 분들은 제가
잘못하고 있는 부분에 대해서도 아낌없는 조언 부탁드립니다…^^
2. 화면 설계
다른 분들도 모두 그렇겠지만 최초의 설계가 개발 완료시점까지 유지되는 경우는 거의 없을
것입니다. 제 경우도 마찬가지여서 최종 결과물의 화면은 최초 설계시의 그것과는 너무나도
딴판이네요. 개발 과정을 기록해두었다가 어떤 변화가 있었는지를 살펴보는 것도 하나의
재미라면 재미겠네요. 참고로 제가 최초 구상한 메인화면은 아래 이미지와 같았습니다.
그리고 최종 결과물은 아래와 같죠
바로 전 글에서 말씀을 드렸지만 Interface Builder(이하 IB)는 UI설계 도구로서도 꽤 훌륭합니다.
사실 첫번째 이미지처럼 러프한 스케치가 선행 되면 더 쉽긴 하겠지만 IB같이 실제 화면상의
모습을 거의 그대로 볼 수 있는 도구가 있다면 더 좋겠죠…^^
변한 것은 UI뿐만 아닙니다. 현재 화면상에 보여지는 정보는 등록된 아이의 이름, 생년월일,
나이, 성별, 띠, 별자리, 탄생석 등이 있습니다만 최초에는 성별, 띠, 별자리, 탄생석 등의
정보가 없고 별 필요없는 태명, 출생지, 담당 의사 등의 정보가 있었습니다. 바꾸길 잘했죠…^^?
그리고 최종적으로 변경된 사항은 이름, 생년월일, 나이…등의 title Label은 이 앞선 버전에서는
배경 이미지에 적혀있었습니다. 좀 더 예쁜 폰트를 사용해보고 싶은데 방법을 몰랐기에
제가 사용할 수 있는 꼼수로 배경 이미지에 Label Text를 넣고 그 위치에 맞게 값들이 보여질
Label 컨트롤드을 배치 했었던 것이죠. 하지만 아이폰 4가 나오고 해상도가 달라졌다는 말이
들리자 '아 이렇게 했다가는 나중에 해상도 차이 때문에 Label과 Value가 어긋나는 경우도
생기겠구나' 하는 생각이 들어 다시 title Label을 Label 컨트롤로 바꾼 것입니다.
지금은 아이폰에서 원하는 폰트를 사용하는 방법을 알았으니 조만간에 업데이트 해서
사용해봐야죠…^^ 폰트 관련 내용에 대서는 링크를 참고하세요. http://www.prapps.net/416
※하단 탭도 변경이 있었는데 이 내용은 이후 글에서 다루도록 하겠습니다.
사족이지만 가장 불만스러운 부분은 아이 사진이 나오는 부분의 사진 프레임입니다.
뭔가 포인트를 주고 싶어 이래저래 만들어봤는데 오히려 시선을 분산시키는 역할만 하고 있네요.
역시 개발자의 디자인 감각이란 한계가 있는가봅니다…^^;;;
그래서 최종적인 화면 구성은 상반부에는 아이에 대한 기본 정보가 출력되고
하반부에는 위쪽에 선택한 아이에 대한 그날의 이벤트를 표시하는 TableView, 아래쪽에는
등록된 아이들을 선택할 수 있는 버튼이 출력될 영역으로 결정이 되었습니다.
3. 실제 소스를 보면…
일단 IB상의 구조를 보시면 다음과 같습니다. 자주 본 이미지죠…^^
Layout상의 가장 하단에 백그라운드 이미지를 보여줄 ImageView가 위치하고 있습니다.
분홍색 그라데이션에 하얀 별 문양이 있는 배경입니다. 이 부분도 진작에 UIView에 배경
이미지를 넣는 방법을 알았더라면 굳이 ImageView를 사용하지 않았을 것입니다만 이 당시에는
그런 방법조차 몰랐네요…^^;;;
참고로 아래와 같은 코드죠.
self.view.backgroundColor = [UIColor colorWithPatternImage:
[UIImage imageNamed:@"cameraControllerBacground.png"]];
이후 다른 화면에서는 위의 방법을 사용하여 배경 이미지를 사용했습니다.
다음으로는 등록한 아이의 사진을 보여줄 ImageView와 별모양의 프레임을 표시해줄 ImageView
그리고 title Label들과 성별, 띠, 별자리, 탄생석 등의 아이콘을 표시해 줄 ImageView, 끝으로
아이의 기본 정보 값을 표시해 줄 Label들이 있네요. 중간에 ImageView하나가 더 있는데
이 것은 테이블 뷰에 표시될 오늘의 이벤트를 둘러싼 프레임인데 이것도 역시 맘에 안듭니다…-.-
이 중에 실제 값이 반영될 컨트롤들은 당연히 코드상에 IBOutlet으로 선언된 객체들과 연결이
되어야 하고 title Label이나 사진 프레임같이 늘 고정된 값만을 가진 것들은 그럴 필요가 없습니다.
일단 이 화면은 탭바 컨트롤러에 연결되어 앱 실행시 내부적으로 호출되어 load되므로
기본 코딩된 initWithNibName…메서드는 주석처리 한 상태입니다. 초기화 코드는 모두
viewDidLoad 메서드에 들어가 있습니다.
나의 실수
저는 viewDidLoad 메서드의 이름만을 생각하고서는 이 메서드가 뷰가 사라졌다가 보여질 때마다
수행되는 메서드라고 생각했었습니다. 그런데 아마도 뷰의 인스턴스가 생성되는 최초 시점에
한번 만 수행이 되나보더군요. 일단 인스턴스가 생성된 뷰에서 뷰가 사라졌다가 다시 보여질
때마다 수행되는 메서드는 - (void)viewWillAppear:(BOOL)animated 메서드를 사용해야
한다는 것을 나중에 알았습니다.
간단하게 내용을 보시면 다음과 같습니다.
NavigationController에 대한 설정 때문에 같은 내용의 코드가 많은 뷰의 viewDidLoad 메서드에
반복적으로 사용되고 있는데 이러한 반복 코드의 공통 함수화도 제가 풀어야 할 숙제네요…ㅠ.ㅠ
CGRect labelFrame = CGRectMake(140.0, 28.0, 170.0, 40.0);
UILabel *label = [[[UILabel alloc] initWithFrame:labelFrame] autorelease];
label.font = [UIFont boldSystemFontOfSize:18];
label.numberOfLines = 2;
label.backgroundColor = [UIColor clearColor];
label.textAlignment = UITextAlignmentRight;
label.textColor = [UIColor blackColor];
label.shadowColor = [UIColor whiteColor];
label.shadowOffset = CGSizeMake(0.0, -1.0);
label.lineBreakMode = UILineBreakModeCharacterWrap;
label.text = @"\nMy Children";
self.navigationItem.titleView = label;
위 내용은 NavigationController의 중앙에 있는 하트모양과 iPhotoDiary가 찍힌 이미지의 우측
밑으로 My Children이라는 텍스트를 출력하는 내용입니다.
다음은 NavigationController의 좌우에 있는 UIBarButtonItem을 설정하는 코드가 나옵니다.
// Configure the add button.
UIBarButtonItem *addButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(addChild)];
self.navigationItem.rightBarButtonItem = addButton;
[addButton release];
UIBarButtonItem *editButton = [[UIBarButtonItem alloc]initWithBarButtonSystemItem:UIBarButtonSystemItemEdit target:self action:@selector(editChild)];
self.navigationItem.leftBarButtonItem = editButton;
[editButton release];
다들 아시는 것 처럼 만일 UIBarButtonItem의 Text를 직접 지정해주고 싶을 경우에는
아래와 같은 메서드를 사용해야 합니다.
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc] initWithTitle:@"저 장"style:UIBarButtonItemStylePlain target:self action:@selector(save)];
self.navigationItem.rightBarButtonItem = saveButton;
[saveButton release];
그리고 얼마전 이 연재를 시작하면서 알게 되어 부랴부랴 수정한 managedObjectContext를
가져오는 코드입니다.
iPhotoDiaryAppDelegate *appDelegate = (iPhotoDiaryAppDelegate *)[[UIApplicationsharedApplication] delegate];
self.managedObjectContext = appDelegate.managedObjectContext;
마지막으로 여러 명의 아이가 등록되었을 때 어떤 아이가 선택되었는지 확인하는 용도로 사용될
문자열 변수인 childButtonName을 초기화 하는 내용이 들어있네요.
많은 컨트롤들을 IB에서 배치하고 IBOutlet으로 연결하였기 때문에 코드가 짧습니다.
그리고 이 화면에서는 데이터를 불러와 출력하는 기능만 있고 데이터 변경이 일어나지는 않기
때문에 fetchedResultsController 메서드는 필요가 없고 fetchRequest를 통해 데이터를
불러오는 내용이 있는 사용자 메서드를 만들어서 사용하시면 됩니다.
제 소스에서는 만일 네비게션바의 우측에 있는 신규 등록 버튼을 눌러 등록 화면으로 갔다가
오는 경우나 좌측의 편집 버튼을 통해 편집 화면에서 수정이나 삭제가 이루어진 경우 그 내용을
반영해야 하기 때문에 앞서 말씀드린 현재 뷰를 벗어났다가 다시 돌아올 경우 매번 수행되는
viewWillAppear:(BOOL)animated 메서드에 데이터를 조회하는 메서드를 호출하는 코드가
들어있습니다. 함수명은 fetchedRequestResult:(NSString *)childName입니다.
코드는 아래와 같네요.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if ([self.childDatas count] == 0 ||
[self.childDatas count] != childCount) {
[self fetchRequestResult:childButtonName];
}
}
여기서 childDatas는 조회된 데이터를 담고있는 NSArray 타입의 객체입니다.
그리고 childCount는 아이의 등록/삭제 등으로 변동이 있었을 때 그 결과를 반영하는 데이터 수를
표시해주는 NSInteger타입의 static 변수입니다.
즉 현재 데이터 건수가 0이거나(최초 실행시) 현재 불러온 데이터 건수와 변경사항을 반영하는
수치가 같지 않은 경우(등록이나 삭제가 발생한 경우) 새롭게 fetchRequest를 통해 데이터를
가져오도록 한 것입니다.
다음으로 가장 중요한 fetchedRequestResult:(NSString *)childName인데요.
이 메서드를 호출함으로써 코어 데이터로부터 데이터를 가져와서 그 데이터를 해당 컨트롤에
출력해 줄 수 있는 것입니다. 이 fetchedRequestResult:(NSString *)childName메서드에서
하는 일은 등록된 아이들의 데이터를 모두 가져와서 다음과 같은 작업을 합니다.
1. Core Data에 접근하여 데이터 가져오기
2. 등록된 아이가 없을 경우 아이 등록을 요청하는 Alert창 띄우기
3. 아이들 수와 성별에 맞는 선택 버튼을 동적으로 생성하여 배치.
4. 등록된 아이들 중 오늘이 백일, 돌 혹은 생일인 경우 Message 창 띄우기
그런데 사실상 1번의 작업이 주된 목적이고 2,3,4번의 작업은 주 목적과 직접적인 관계가
없기 때문에 다른 메서드로 분리시키는 것이 좋을 것입니다.
그리고 애초에는 계획이 데이터를 불러오는 시점에서 모두 불러올 것이 아니라 선택된
아이의 데이터만을 불러올 생각으로 인자로 아이 이름 문자열을 넘기도록 했었는데요
나중에 그냥 전체를 다 불러와서 처리하는 것이 더 나을 것 같아 수정을 했습니다. 그래서
메서드 인자인 (NSString *)childName는 의미가 없어졌네요…-.-
이런 부분이 숙제로 남아있습니다…ㅠ.ㅠ
마지막으로 위에서 배치된 버튼을 선택함에 따라 선택된 버튼의 아이 정보를 화면에 출력해주는
setDatatoControlls:(ChildData *)cData라는 메서드가 있네요. 여기서는 인자로 받은 ChildData의
인스턴스를 통해 IB에서 배치한 각각의 컨트롤들에 실 데이터를 출력하고 있습니다.
어려운 부분은 생년월일이라는 기본 정보를 가지고 양/음력 생일, 띠, 탄생석, 별자리 등을
계산하는 부분이고 나머지는 그냥 해당 컨트롤에 값을 대입하기만 하면 됩니다.
고민...
기본 정보로 가공이 필요한 경우, 예를 들면 생년월일을 통해 띠와, 탄생석과 별자리가
정해집니다. 이 때 이러한 가공된 데이터들을 저장시에 미리 계산하여 DB의 별도 필드에 넣고
사용을 할 것인지 아니면 지금처럼 기본 정보만 넣고 가공 데이터는 출력시 가공작업을 거쳐
보여줄 것인지같은 문제가 미묘하게 신경을 건드리는 부분이 있습니다.
전자를 택하자니 DB를 운용하는데 있어 조금이라도 부하를 덜 주고 싶은 마음이 생기고
후자를 선택하자니 아무래도 입력 보다는 조회의 빈도수가 높은데 그런 부분에 데이터 가공이라는
작업 부하를 주고 싶지 않은 마음도 생기고…
다행이 데이터 건수가 몇건 안되기 때문에 어떤 방식을 쓰건 상관이 없겠지만 대량의 데이터를
처래 해야 하는 경우에는 충분한 부하 계산을 거쳐서 방법을 정하는 것이 좋을 것 같습니다.
그게 힘든 것이 우리 개발자들의 현실이지만 말이죠…ㅠ.ㅠ
나머지는 단순히 버튼이 클릭되었을 경우의 이벤트 처리를 위한 메서드들이네요.
4. 요약
본격적으로 실제 소스를 살펴보다 보니 정말 미흡한 부분이 많은 소스코드네요…^^;;;
무식하면 용감하다고…용케도 이런 코드를 남에게 공개하고 또 이 것을 바탕으로 소스 분석을
하겠다는 야무진 계획을 세웠습니다…
실제 소스를 분석한 부분이라 딱히 요약이라기엔 뭐하고 팁이라고 할만한 것을 보면
1. 네비게이션 컨트롤러의 타이틀을 사용자가 직접 지정하는 방법. 아래 코드입니다.
CGRect labelFrame = CGRectMake(140.0, 28.0, 170.0, 40.0);
UILabel *label = [[[UILabel alloc] initWithFrame:labelFrame] autorelease];
label.font = [UIFont boldSystemFontOfSize:18];
label.numberOfLines = 2;
label.backgroundColor = [UIColor clearColor];
label.textAlignment = UITextAlignmentRight;
label.textColor = [UIColor blackColor];
label.shadowColor = [UIColor whiteColor];
label.shadowOffset = CGSizeMake(0.0, -1.0);
label.lineBreakMode = UILineBreakModeCharacterWrap;
label.text = @"\nMy Children";
self.navigationItem.titleView = label;
2. 단순 조회성 화면이므로 fetchedResultsController 메서드는 필요가 없고
fetchRequest를 통해 데이터를 불러와 화면에 출력하면 된다는 것.
요정도가 되겠네요.
5. 마무리
대부분이 IB에서의 작업이라 실제 코드 분량은 그리 많지 않은 화면이었습니다.
setDatatoControlls:(ChildData *)cData 메서드에서 띠, 탄생석, 별자리 계산하는 내용과
각각의 값에 맞는 아이콘을 구분해서 지정해주는 내용이 좀 길다면 기네요.
다음 시간에는 메인 화면과 연관된 아이 등록 화면과 편집 화면에 대해 살펴보도록 하겠습니다.
혹시 시간이 부족한 경우 둘 중의 한 화면만 진행하겠습니다…^^;;;
새로운 한 주 잘 보내시고 다음에 다시 뵙겠습니다.
긴 글 읽어주셔서 감사합니다.
'Development > iPhotoDiary(BabyPhotoDiary)' 카테고리의 다른 글
[옛 글] [실전 소스 분석] 8. 테이블 뷰 컨트롤러 써보기 - 2 (0) | 2013.07.18 |
---|---|
[옛 글] [실전 소스 분석] 8. 테이블 뷰 컨트롤러 써보기 - 1 (0) | 2013.07.18 |
[옛 글] [실전 소스 분석] 6. 그림 좀 그려볼까? (0) | 2013.07.18 |
[옛 글] [실전 소스 분석] 5. 코어 데이터의 끝 (0) | 2013.07.18 |
[옛 글] [실전 소스 분석] 4. 이제 Core Data좀 써보자! (0) | 2013.07.18 |