UITextView 에서 단어 개별에 관한 특정 작업(색을 다르게 입힌다던가, 애니메이션, 이미지 추가)을 위해서는 단어가 출력되는 좌표와 크기를 알아야 합니다. 그 개별 개별 단어의 크기와 좌표를 알아내는 방법에 관한 글 입니다. 

iPhone apps 개발하는 중에 문장이 쓰여져 있는 UITextView 에서 특정 단어에 효과를 주고 싶었습니다. UIWebView 를 이용해서 화면에 글을 출력하고 CSS 와 Javascript 를 이용해서 개발하면 된다는 소리가 있기는 하던데, 제가 잘 아는 분야와는 조금 거리가 있어서, UITextView 를 수정하기로 했습니다. 

원리

원리는 쉽습니다. UITextView 에서 문장을 출력하는 경우라고 하면

1. 문장을 각 단어별로 쪼개서 NSArray 로 저장합니다. 

2. 쪼개진 단어가 표시되는 좌표와 테두리 크기를 알아냅니다. 

3. 변경을 주고 싶은 단어 위에 같은 크기(단어 와)의 UILabel 을 살짝 얹어줍니다. 

4. UILabel 에 이미지나 폰트, 글자색을 변경해 줍니다. 



즉 이렇게 보이는 화면이 있으면 , 아래쪽에 보이는 'Hello' 라는 버튼을 터치하면 



이런식으로 각 단어들이 보이는 듯이 색이 입혀집니다. 

구현

원리는 쉽지만, 막상 구현할려고 하니 힘들더군요. 비슷한 코드가 있나 찾아보는데만 반나절 이상을 소비했습니다. (결국 못찾아서 직접 만들어 줬습니다) 화면에 문장이 출력되는 부분에서 패딩을 생각 못해줘서 또 시간 많이 소비했구요. 

Object-C 와 아이폰 개발에 익숙치 않아서 , 문법이 이상한 것들이 조금 보이더라도 고수분들께서는 이해해주시기 바랍니다. (지적도 감사합니다 )


- (IBAction) rePaintWord: (id) sender 
{
    // color Array 를 만든다. 
    NSArray * color_array = [NSArray arrayWithObjects:[UIColor grayColor] , [UIColor redColor] , 
                                     [UIColor greenColor], [UIColor blueColor], [UIColor yellowColor],
                                     [UIColor orangeColor], [UIColor brownColor], nil];
    

    // 단어들 tokenizing 한다
    NSArray * myWords = [overLookTextView.text componentsSeparatedByString:@" "]; // 문장에 쓰인
    NSInteger count = [myWords count] ;

    // 실제로  contents 가 뿌려지는 textview 크기 보정을 한다. padding 크기를 
    // 보정 안해주면 반나절 이상 테스트 한다고 해도 제대로 결과가 나오지 않는다. 
   
    // padding size 이 패딩 사이즈는 일명 마법숫자(magic number)이다.
    // 원리도 모르고 어딘가에서 표시도 되지 않았다. -ㅅ- 
    int padding_size = 11; 

    CGSize content_size = CGSizeMake (overLookTextView.contentSize.width - padding_size ,
                                      overLookTextView.contentSize.height - padding_size);

    // 높이가 변하게 되는 단어를 지정하기 위한 산물이다. 즉 갑자기 높이가 변하는 단어는 lineBreak
    // 됐다고 보면 된다. 바로 그 시점의 높이와 Range 를 저장하기 위해서 이다. 

    CGSize last_string_size = CGSizeMake(0,0);
    NSRange last_result_range = NSMakeRange(0, 0);

    NSRange result_range = NSMakeRange(0, overLookTextView.text.length);

    for(int i = 0 ; i < count ; i++)
    {

        // 전체 문장에서 정확히 원하는 단어를 검색하기 위해서 이다. 정규표현식을 썼으며 
        // 단어 인 것들만 찾기 위해서 썼다. NSRegularExpressionSearch 는 
        // iOS 4.0 이상부터 지원한다고 한다. 
        result_range = [overLookTextView.text 
                         rangeOfString:[NSString stringWithFormat:@"\\b%@",
                          [myWords objectAtIndex:i]]
                         options:NSRegularExpressionSearch range:result_range] ;
       

        // 실제로 찾고자 하는 단어가 차지하는 영역 크기 
        CGSize wordSize = [[myWords objectAtIndex:i] 
                        sizeWithFont:overLookTextView.font forWidth:content_size.width 
                        lineBreakMode:UILineBreakModeWordWrap];


        // 시작부터 단어를 포함한 줄까지 잘라내기 
        NSString* head = [overLookTextView.text 
                  substringToIndex:result_range.location + result_range.length];

        // 위에서 잘라낸 단어줄의  content 크기 알아내기 , 이는 높이를 비교하기 위해서
        //  알아보는 것이다. 지난번 결과로 저장된 높이랑 틀려진다면
        // 줄이 바꼈음을 알고 바로 그 시점이 마지막 범위와 높이가 저장되는 시점이다. 
        CGSize string_size = [head sizeWithFont:overLookTextView.font 
               constrainedToSize:content_size lineBreakMode:UILineBreakModeWordWrap];
        if (string_size.height != last_string_size.height)
        {
            last_result_range = result_range ; // 줄바뀌는 첫번째 단어 의 시작 위치를 저장
            last_string_size = string_size ;
        }


        // head 는 문장의 처음부터 단어를 포함하고 있는 문자열까지 잘라낸 것이다. 
        // tail 은 줄이 바뀐 첫 단어부터 단어를 포함하는 곳까지 잘라낸 것이다. 
        NSString * tail = [head substringFromIndex:last_result_range.location];

        // lineSize 는 줄이 바뀐곳부터 단어를 포함한 곳까지의 크기를 얻어내서 저장하는 곳이다. 
        CGSize lineSize = [tail sizeWithFont:overLookTextView.font forWidth:content_size.width 
                                                    lineBreakMode:UILineBreakModeWordWrap];

        // temp_rect 는 단어가 위치하는 좌표값과 크기가 저장된다. 하지만 이는 
        // 실제로 위치한 곳과 확실하게 일치시켜 줄려면
        // textView 가 위치한 실제 좌표 값과 글자가 실제로 출력되는 
        // 위치까지의 'padding' 값을 더해 주어야 한다. 
        // 심각한 문제는 여기서 더해주는 padding 은 위에서 content 
        // 윈도 크기를 보정하기 위해서 더해주는 padding_size 값과는
        // 다르다는 것이다. 

        CGRect temp_rect = CGRectMake (lineSize.width - wordSize.width , 
                         last_string_size.height - wordSize.height ,
                                       wordSize.width , wordSize.height);


        // Test 삼아서 위치보정해주고 (textview , padding) 실제 글자 위치에 
        // 작은 Label 을 한개 만들어서 덮어놓는 함수를 작성했다.
        [self make_label_on_screen:[myWords objectAtIndex:i] 
              rect:temp_rect color:[color_array objectAtIndex:( i % [color_array count]) ]];

        // 검색 범위를 변경해 주는 곳이다. 단어를 찾은 크기만큼 이동해 주는 모듈
        int new_location = result_range.location + result_range.length ;
        result_range = NSMakeRange(new_location , overLookTextView.text.length - new_location);
    }
        
}

- (void) make_label_on_screen:(NSString *) word rect:(CGRect) 
                                        label_rect color:(UIColor *) color
{

    // 실제 위치를 표기해 주는 패딩과 textview 위치 찾기
    CGFloat ui_padding_size = 7.9;

    label_rect.origin.x += overLookTextView.frame.origin.x ; 
    label_rect.origin.y += overLookTextView.frame.origin.y ; 

    label_rect.origin.x += ui_padding_size;
    label_rect.origin.y += ui_padding_size;



    UILabel * nameLabel = [[UILabel alloc] initWithFrame:label_rect];
    nameLabel.text = word ;
    nameLabel.textColor = color;
    nameLabel.backgroundColor = [UIColor clearColor];
    nameLabel.textAlignment = UITextAlignmentRight;
    nameLabel.font = overLookTextView.font ;
    
    [self.view addSubview: nameLabel];
    [nameLabel release];
}
'Hello' 버튼에 대응되는 함수와 UILabel 을 한개 얹어주는 함수 두개를 만들어 줬습니다. 혹시 몰라서 Test 로 만들어본 프로젝트를 첨부 합니다. 




UIImage 를 이용해서 화면에 출력하는데 정상적으로 출력시키는데도 화면상에서 계속해서 이미지가 반전해서 뿌려집니다. 열심히 구글링 해서 답을 알아냈습니다. 그 문제에 답을 올린 사람들하고 마음이 공유되는군요. '왜 애플은 모든것을 귀찮고도 어렵게 만드는 것일까?'

어찌됐건 해결을 했습니다.

원문보기: http://stackoverflow.com/questions/506622/cgcontextdrawimage-draws-image-upside-down-when-passed-uiimage-cgimage/511199#511199

UIImage *image = [UIImage imageNamed:@"testImage.png"];    
CGRect imageRect = CGRectMake(0, 0, image.size.width, image.size.height);      

CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);

CGContextDrawImage(context, imageRect, image.CGImage);

위와 같은 식으로 코딩을 하면 반전되는 현상을 해결 할 수가 있다고 합니다. 좌표계 일치 시켜줘야 한다는 것이지요. 제일 이해가 안가는 것이. UIImage 의  drawInRect 메서드를 사용했을 경우 이미지가 반전이 되는 것이지요 ^^;

암튼 해결했습니다. 슈슝..

최근에 개발을 진행하게 되었습니다. 개발환경에 대한 설명은 간단하게
말씀드리자면 기존의 심리행동을 측정하는 프로그램이 상당히
낙후됐습니다. DOS 시절 프로그램을 쓰고 있는 현실이지요. 윈도
어플리케이션이 있지만 이 또한 Windows 3.1 시절의 프로그램입니다. 물론
이들이 요즘 나오는 OS 에 설치가 된다고는 하지만 아무래도 오래전
프로그램 이다보니 효용성이 많이 떨어지는 환경입니다.

이 프로그램이 어떻게 쓰이는 지에 대해서 알아보기로 합니다.

조그만 방에서 어린이와 어머니가 한방에 있습니다. 그리고 관찰자들은 방
밖에서 어린이를 관찰하며 정해진 시간동안 어린이가 보여주는 행동을
관찰하여 그 행동사항을 기입하는 프로그램 입니다.

커다란 방과 중앙통제실에서 프로그램을 띄우고 방안이 보이는 유리창은
물론 관찰대상의 얼굴이 클로즈업 된 카메라가 설치되서 그 모든 행동을
녹화하면서 진행되는 영화같은 장면이 아니기 때문에 , 실제로 실험은
상당히 조악합니다. (영화에 비하면 말이죠)

그냥 프로그램이 설치된 노트북을 들고 조그만 유리창으로 쳐다보면서
관찰의 내용을 기입하는 방식인데, 노트북이 참 편리한 컴퓨터이긴 하지만
아무래도 이 실험에 대한 휴대성은 떨어지지요.

그래서 PDA 를 가지고 이런 작업을 하면 유용합니다. 노트북 대신 PDA를
들고 관찰을 하는 것이지요. 크기가 작으니 한손에 들고 쳐다보면서 상황이
발생할때마다 클릭 클릭 하는 것입니다.

프로젝트의 주요 목적은 관찰대상을 관찰하며 얻어지는 데이터를 편하게
저장할 수 있는 PDA 용 데이터 수집기를 만드는 것이 목표입니다.

개발 환경

OS : Windows XP , Windows Vista
TOOL : Visual Studio 2008 Professional Beta
     - 돈이 안들길래 받아서 설치했습니다.
Target Machine : SPH-M8100

구체적으로 개발은 Mobile 환경에서 개발하게 됩니다. 그를 위해서 기존의
개발툴만 깔려 있는 상태에서는 개발이 이루어 질 수가 없습니다. 개발용
SDK 와 테스트를 위해서 필요한 기기 Image 가 필요합니다.

1. Windows Mobile 5.0 Pocket PC SDK 를 설치한다.
   - 삼성 SPH-M8100 은 포켓피씨 OS 고 관련 SDK 를 설치해야
   포켓피씨에서 돌아가는 프로그램을 만들 수가 있다.
2. Windows Mobile 6 Professional Images (KOR) 이 설치한다.
   - 테스트를 위해서 이미지 종류중에서 KOR 버젼을 골라서 설치를 해야
   한다.

위의 두가지가 제대로 설치됐으면 설치 됐는지 확인하는 과정이
필요합니다. 간단한 Hello World 어플리케이션 한번 만들어 보기로 하지요

사용자 삽입 이미지

 ( 우.. 이 그림 캡쳐해서 올리다 보니 그림 크기 같은거 조절이 힘드네요. 회사에서 만든 웹 에디터로 글 쓰고 싶다는 생각이 팍팍 드네요 ㅎㅎ )

SmartDevice 에서 MFC Smart Device Application 선택해서 프로젝트 이름 기입해주면 됩니다.
 - 글씨체가 이상한건 취향입니다 ^^;

그 다음 진행은 Wizard 화면이 나옵니다. 적당히 "Next" 버튼을 눌러서 다음 화면에서 개발 플랫폼을 정해줍니다.

사용자 삽입 이미지

SPH-M8100 같은 경우는 Pocket PC SDK 5.0 으로 충분하고요, 블랙잭 의 경우는 5.0 SmartPhone SDK 로 컴파일 해야 합니다. 사실 둘의 차이는 타겟 플랫폼의 차이일뿐 개발요소는 거의 동일합니다.

 
사용자 삽입 이미지

간단한 application 이니 Dialog Based 로 만들어 보기로 하지요 , 실제로 PDA 자체에서 돌아가는 프로그램을 만들때는 Single Document 를 선호하긴 합니다. 몇가지 이유에서 말이죠
그리고 SPH-M8100 은 한글 OS니 리소스를 한국어로 설정하는 것 잊지 마시고요.

그 다음에 "Next"를 연타해서 프로젝트를 생성합니다.

사용자 삽입 이미지
프로젝트에서 "Resource" 탭을 여시고 "HelloWorldppc.rc" 쪽 리소스를 엽니다. 이름에서 눈치 채셨겠지만, ppc 는 Pocket PC 용 입니다. sp 라고 붙은 것은 스마트폰 용이지요.

다음과 같이 가운데 부분의 Static Text Control 에 적혀 있는 내용을 "Hello World" 로 바꾸어 줍니다.

사용자 삽입 이미지

이제는 컴파일 하고 가상 머신 이미지에 배포해보는 일만 남았습니다.

사용자 삽입 이미지

위에 타겟 플랫폼을 Pocket PC 용인지 확인하시고 빌드를 시작합니다.

사용자 삽입 이미지

빌드 성공!! 이제 준비된 가상 머신에 배포를 해보도록 하지요.

사용자 삽입 이미지
위 그림을 보시면 아시겠지만, 기본으로 제공된 가상머신 이미지에는 한국어 버젼이 없습니다.
그래서 가상머신을 바꿀 필요가 있는데 솔루션의 속성창을 엽니다. (여는 방법은 Solution tab 에서 오른쪽 마우스를 클릭하시고 맨 아래에 있는 "property" 메뉴를 클릭합니다.)

사용자 삽입 이미지
"Deployment" 부분에서 Deployment Device 부분을 KOR Windows Mobile 6 Professional Emulator 로 바꾸어 줍니다. 그리고 변견된 부분을 적용하시고 설정창을 닫으신 다음에 Debug 메뉴에서 "Start Debugging" 을 선택해주시면 됩니다. ( Visual Studio 6.0 스타일로 해둔 저는 F5 를 누르면 됩니다 )

사용자 삽입 이미지


 눈이 좀 침침해서 큰 화면이 필요하다고 생각되시면 KOR Windows Mobile 6 Professional VGA Emulator 로 배포하시면 됩니다. 한 4배정도 되는 화면이 나타납니다 ^^

한글 OS 니까 , 한글로 출력도 해보았습니다. 아까 Hello World 라고 친 Static Text 에서 한글로 바꿔주기만 해도 됩니다.

사용자 삽입 이미지

여기까지 Pocket PC용 프로그램을 만들기 위한 기본환경 세팅을 알아보았습니다. 이정도만 해도 기존의 MFC 프로그램에 익숙하신 분들은 생각나는 모든 형태의 프로그램을 만드실 수가 있을 것입니다.

다음에는 실제로 실용 팁 정도로 꾸며볼 예정입니다. (너무 밝히면 계약 위반이라 ...) 몇가지 기능이 추가된 페이지 한개만 만들어 보는 것으로 꾸밀 예정입니다.

+ Recent posts