최근 앱을 만들때 중요도에서는 떨어지지만 반드시 들어가는 기능을 꼽는다면 튜토리얼(Help) 화면일 것이다.

아무도 중요하게 생각안하는 이 화면이 릴리스 거의 막판이되면 디자인때문에 종종 귀찮아지곤 한다.

예를 들어 "이 앱에 대해서 자세히 알고 싶으시면 여기를 클릭하세요" 라는 문구에 "여기"를 클릭하면 화면이동을 하게 한다던지 사파리로 넘겨준다는가 하는 단순한  것들이다.


이런기능들을 예전엔 UIWebView로 구현했었다. (네이티브 화면이동의 경우엔 딜리게이트를 이용하여 후킹하는 방법으로)


그러나 링크기능때문에 UIWebView를 사용한다는건 마치 "Hello World"를 출력하기 위해 멀티스레드로 코드를 짜는것과 같이 미련한 짓이라는 생각이 들어 간단한 방법을 찾아보기로 했다.






역시나! 구현하는 방법이 있었다.


첫번재 방법. UILabel을 이용한 링크 구현이다.

지금까지 조사한 결과 모양만 흉내가능하고 실제 링크터치 이벤트는 못만드는듯 하다.


NSString *text = @"한지민짱! 그럼 여길 클릭하세요.";

    NSRange find_range = [text rangeOfString:@"여길"];

    linkLabel.userInteractionEnabled = YES;

    NSMutableAttributedString *textString = [[NSMutableAttributedString alloc] initWithString:text

                                                                                   attributes:@{ NSForegroundColorAttributeName:[UIColor blackColor],

                                                                                                 NSFontAttributeName:[UIFont systemFontOfSize:17.f]}];

    NSMutableAttributedString *linkString = [[NSMutableAttributedString alloc]

                                             initWithString:[text substringWithRange:find_range]

                                             attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:24.f],

                                                          NSLinkAttributeName:@"native://popuplink/?touched"}];

    

    

    //UILabl 이용하는 방법..그러나 클릭안됨.

    [textString replaceCharactersInRange:find_range withAttributedString:linkString];

     linkLabel.attributedText = textString;


두번째 방법은 UITextView로 링크 구현하는 것이다. 물론 delegate를 이용하면 UIWebView처럼 이벤트 후킹이 가능하다.


    NSString *text = @"한지민짱! 그럼 여길 클릭하세요.";

    NSRange find_range = [text rangeOfString:@"여길"];

    linkLabel.userInteractionEnabled = YES;

    NSMutableAttributedString *textString = [[NSMutableAttributedString allocinitWithString:text

                                                                                   attributes:@{ NSForegroundColorAttributeName:[UIColor blackColor],

                                                                                                 NSFontAttributeName:[UIFont systemFontOfSize:17.f]}];

    NSMutableAttributedString *linkString = [[NSMutableAttributedString alloc]

                                             initWithString:[text substringWithRange:find_range]

                                             attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:24.f],

                                                          NSLinkAttributeName:@"native://popuplink/?touched"}];

    

    [textString replaceCharactersInRange:find_range withAttributedString:linkString];

     linkTextView.attributedText = textString;


//UITextView 이용한 방법. 클릭이벤트 발생됨.

    linkTextView.scrollEnabled = NO;

    linkTextView.editable = NO;

 linkTextView.selectable = YES;

    linkTextView.textContainer.lineFragmentPadding = 0;

    linkTextView.textContainerInset = UIEdgeInsetsMake(0, 0, 0, 0);

    linkTextView.delegate = self;

    linkTextView.attributedText = textString;

    

    //사이즈 맞춤.

    [linkTextView sizeToFit];

    

    //사이즈를 맞추기 위해 이거 메소드를 호출해줘야 한다고함.

    [linkTextView layoutIfNeeded];

    [linkTextView setTextContainerInset:UIEdgeInsetsMake(0, 0, 0, 0)];

    

    //텍스트뷰는 텍스트를 넣어둔후 폰트설정을 해야 적용된다능...

    linkTextView.font = [UIFont systemFontOfSize:13.f];

}



#pragma mark - UITextView delegate methods

- (BOOL)textView:(UITextView *)textView shouldInteractWithURL:(NSURL *)url inRange:(NSRange)characterRange

{

    NSLog(@"%s", __FUNCTION__);

    

    NSString *scheme = [url scheme];

    NSString * query = [url query];

    NSURL *path = [url absoluteURL];

    NSLog(@"scheme : %@   query : %@   path : %@ ", scheme , query, path);

    

    

    return NO;

}








돌려보니 잘 된다. 웹뷰쓰는것보다는 기분상 더 좋아 보인다.




Posted by 악당잰 트랙백 0 : 댓글 0

댓글을 달아 주세요

많은경우 여러개의 리턴값을 받기위해 참조를 이용한 값전달 받기를 한다. 이러한 방식은 에러를 전달할 때 가장 많이 사용되므로 아마도 익숙할 것이다. 


보통 이런방식으로..


//output parameter를 사용한 메소드

- (BOOL)huga:(NSString*)s error:(NSError**)outError {


    if (outError) {

        *outError = [NSError errorWithDomain:@"domine" code:-1 userInfo:nil];

        return NO;

    }

    return YES;

}



//사용하기

NSError *error = nil;

BOOL success = [self huga:@"hahaha" error:&error];



그런데 유심히보면 포인터를 두개를 사용한 참조를 이용하는데 왜 그럴까? 그 이유를 내 나름대로 해석하자면 이렇다.

만약 hoge라는 메소드를 만들고 출력 파라메타를 이용한다면 이런식으로 구현할 것이다.


- (void)hoge:(NSString **)outValue {

    *outValue = @"Kjcode!";

}


//값을 줄때 요렇게..

NSString *str = nil;

[obj hoge:&str];




이런식으로 구현된 소스는 컴파일러에서 아래와 같이 해석한다고 한다. 


//complier code.

- (void)hoge:(NSString *__autoreleasing *)value {

    *value = @"Test";




//complier code.

NSString *__strong s = nil;

NSString *__autoreleasing temp = str;

[obj hoge:&temp];

str = temp;




이 이야기는 참조로 넘어가는 값을 __autoreleasing을 해줌으로 인하여 ARC를 적용시켜주는 것이다.

뿐만아니라 시각적으로도 개발자가 포인터에 값을 넣어주어야 하기때문에 반환되는 파라메터라는 것을 금방 눈치챌 수 있을것이다.


만약 포인터 딸랑 하나로 output파라메타를 사용하는 메소드를 만들었다면 문제가 생길소지가 다분하다.

예를 들어 플젝 개발자가 경험이 부족하거나 아니면 경험자라도 메소드안을 꼼꼼히 보지 않는다면 아무생각없이 그 파라메터를 strong프로터피에 값을 전달시킬수도 있을것이다.

그럼 그순간 순환참조가 일어날 것이고 영원히 메모리에서 젖은낙엽처럼 붙어있게 될것이고 짜증나는 야근을 경험할지도 모른다.



휴~ 지금 내가 하는 플젝에서도 출력파라메터를 잔뜩 사용해놓구선 메모리를 잔뜩 잡아먹는 코드를 만들어놔서 고생중인데 물론.. 그 당사자는 이미 그만둔 상태이므로 리팩토링은 남은자의 몫이 되어 버렸다.


암튼 개고생중. (여기서 "개"라는 표현은 욕이 아닌 강조를 위한 최상급 표현임.)



참고사이트

https://developer.apple.com/library/ios/releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html




Posted by 악당잰 트랙백 0 : 댓글 2

댓글을 달아 주세요

  1. addr | edit/del | reply 김기봉 2015.02.03 08:26

    형 이부분
    //complier code.
    NSString *__strong s = nil;
    NSString *__autoreleasing temp = str;
    [obj hoge:&temp];
    str = temp;

    에서
    NSString *__strong s = nil; -> str = nil; 이죠?