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 로 만들어본 프로젝트를 첨부 합니다. 





충격으로 정신이 나가 있는 상태임, 아무리 교육용 게임이라서 영어 단어 많이 알고 있는 사람이 강하다지만.. 완벽하게 '발렸' 습니다. OTL

이 나이에 벌써 손이 느려질리도 없고, 동체시력도 왠만큼 된다고 자부하는데.. 그냥 초장부터 연속으로 계속 발렸습니다. 흙 흙 흙..

회사내에서 그리 강한편은 아니였지만 어디가서 영어 단어로 꿀리지 않을 정도라고 여겼는데.. 이거 원 쪽도 못쓰니..

한밤에 넋두리 쓰고 갑니다. ㅜ.ㅜ

회사에서 서비스하는 digdic inuit 님에게 보여드렸더니 재밌는 개념이라고 하시고, 몇가지 추가되어야 할 사항에 대해서 지적해 주셨는데 그중 뼈 아픈 부분이
"체험하기" 였습니다. 그래서 잽싸게 체험하기 부분을 추가했습니다.


물론 회원 가입을 하면 이점이 많이 있습니다.

 - 사용자 각각의 오답 관리를 체계화 해서 자주 틀리는 단어들을 자주 노출시켜주는 기능
 - 목표를 세우고 목표를 완료할 때까지의 일정 관리
 - 익히고 있는 어휘 수준을 레벨로 표기하여 자신의 어휘수준을 알 수 있게 하기
 - 놀이방 사용시 점수쳬계를 정리해서 순위로 나타내기
 - 다양하고 더 많은 컨텐츠로 학습과 놀이가 가능함

등등이 있을 수 있겠습니다. 지금까지는 충분히 사람들이 많이 참여해 주시고 있는데, 이 여세를 계속 몰아서 가야 할텐데요... ㅎㅎ

많이 오셔서 가입도 하시고, 피드백도 남겨주세요.

digdic 서비스 바로가기
칠선벽파

칠선벽파 의 그림입니다. 순발력과 재치를 필요로 합니다.



(서울=연합비즈뉴스) 교육용 소프트웨어 전문 개발ㆍ서비스회사인 널리(대표 허원근, www.digdic.com)는 학습과 게임을 결합한 영어학습서비스 '디그딕(Digdic)'를 출시했다고 17일 밝혔다.

디그딕은 영어 학습자들이 지루한 암기방법으로 인해 영어단어 학습에 어려움을 겪는다는 점을 보완, 학습과 게임을 과감하게 결합해 매일 짧은 시간을 활용해 엄선된 시험 대비 영어단어 콘텐츠를 학습할 수 있는 영어학습서비스다.

디그딕은 학습과 게임을 통해 학습자가 본인도 모르는 사이에 같은 단어를 몇 차례 접하게 되면서 자연스럽게 반복학습이 된다. 또한 개인별로 일정관리를 제공해 학습자는 일정에 따라 목표로 하는 시험에 맞춰 단어학습을 할 수 있다.

특히 퀴즈형식 게임을 통해 쉽고 재미있게 단어를 공부할 수 있고, 순발력과 상황판단을 요구하는 짝 맞추기 형식의 치열한 대전 게임을 통해 단어를 보는 순간 뜻이 생각나는 인지적 확인 훈련도 가능하다. 순위시스템이 도입된 각 게임은 경쟁심과 함께 지속적인 도전을 자극해 학습 참여를 유도한다.

디그딕은 11만개의 수준별 영어단어 콘텐츠를 내장했고, 다양한 영어자격시험과 전문영역에 맞추어진 엄선된 어휘도 제공한다.

널리 허원근 대표이사는 “디그딕은 학습 능률을 높이는 다양한 특화 기능을 갖추고 있다”며 “학생이나 직장인이 인터넷을 통해 하루에 15분만 투자하면 속도감 있는 영어학습을 할 수 있다”고 밝혔다.

디그딕 학습서비스는 홈페이지(www.digdic.com)을 통해 접속 가능하며 회원 가입 시 일정기간 무료 사용이 가능하다.

기사 원문보기: http://news.naver.com/main/read.nhn?mode=LSD&mid=sec&sid1=102&oid=001&aid=0003172862
다음에 올라왔네요. 회사에서 저 딴거 하는 동안 잽싸게 처리 했군요 ^^;

http://media.daum.net/press/view.html?cateid=1065&newsid=20100312134913900&p=newswire

영어 단어 게임 <-- 이걸 키워드로 찾을 수 있습니다. 대부분 게임들이 플래쉬 게임인 상황에서 클라이언트 경쟁 게임 분야는 아직 없는 거 같네요.

방향은 제대로 잡은거 같은데, 이제 포장과 마케팅만이 남았네요.

+ Recent posts