Things take time

[SWIFT] TextView에 링크 걸기 및 뷰 컨트롤러 이동 본문

iOS (기능)

[SWIFT] TextView에 링크 걸기 및 뷰 컨트롤러 이동

겸손할 겸 2020. 1. 16. 10:58

[TextView vs Label]

 

문자열을 나타낼 때 사용하는 일반적인 컴포넌트는 UITextView와 UILabel이다. 차이점이야 짧은 줄 표시, 스크롤 가능 여부 등 다양한 속성이 있겠지만, 만약 여기에 링크를 걸고 싶다면 어떻게 해야할까

 

나 같은 경우, 뷰 안에 라벨을 넣어 사용하고 있었는데, 이번 링크 기능을 통해 텍스트 뷰로 변환 했다.

 

 

[기본 링크 사용 방법]

import UIKit

class ViewController: UIViewController, UITextViewDelegate {

    @IBOutlet weak var tv: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tv.delegate = self
        tv.text = "제 블로그는 https://g-y-e-o-m.tistory.com 입니다"
        tv.isEditable = false
        tv.dataDetectorTypes = .link
    }
}

** 중요

링크를 걸때 isEditable = false 및 isSelectable = true(Default) 및 isUserInteractionEnabled = true 가 필수 조건이다.

 

그리고 실행하게 되면 

위와 같이 자동 링크를 선별하여 걸리게 된다. 클릭하면 사파리가 켜지면서

위와 같이 사파리앱으로 이동되게 된다.

안드로이드의 경우, Linkify가 대응 되는 기능이었고 그를 확장시켜 커스터마이징, 스키마를 통해 방법을 넓혔다면, iOS의 경우 방법이 더 간단하다.

 

 

[코드]

    func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        let storyboard = UIStoryboard(name: "Main", bundle: nil)
        if let webViewVC = storyboard.instantiateViewController(identifier: "WebViewController") as? WebViewController{
            webViewVC.url = URL
            self.present(webViewVC, animated: true, completion: nil)
        }
        return false
    }

위에서 UITextViewDelegate를 상속받았고 .delegate = self를 해줬기 때문에, 위와 같은 함수를 사용할 수 있다. 영단어만 봐도, URL타입의 데이터와 interact, 상호작용할 때 호출되는 것으로써 return type이 true일 경우(Default) 사파리가 켜지게 되고, false일 경우 작업을 수행하지 않는다. 그러므로 내가 원하는 뷰 컨트롤러로 이동하겠다면 위와 같이 사용하면 된다.

 

그리고 다시 링크를 클릭하면 아래와 같이 웹뷰 컴포넌트를 포함한 뷰 컨트롤러가 열리게 된다.

 

** 추가

이 방식을 택하면 편리하긴 하나, isSelectable이 활성화되어, 텍스트를 꾹 누르게 되면 복사, 찾아보기, 공유하기 등의 기능이 활성화 된다. 해당 기능을 막기위해, isUserInteractionEnabled 등을 빼 버리면, 링크걸린 텍스트를 클릭해도 이동할 수 없다.

 

그래서 텍스트를 꾹 눌러도 내가 원하는 작업만 하길 원하며, 링크도 걸리길 원한다면, 텍스트뷰를 사용하지 말고, UILabel을 이용하여 완전히 커스터마이징 해야한다.

 

 

[isSelectable을 사용하지 않으면서 링크 열기]

만약 UITextView에 dataDetectorType를 통해 자동적으로 링크잡아주는게 편리하여, 이 기능을 쓰되 단 isSelectable을 막고 싶다면 어떻게 할까 UITextView를 재정의 해야한다. 

 

UITextView를 상속받는 새로운 클래스를 하나 생성한다.

import UIKit

class SubTextView: UITextView {
    
    /*
    override func point(inside point: CGPoint, with event: UIEvent?) -> Bool {
        guard let pos = closestPosition(to: point) else { return false }

        guard let range = tokenizer.rangeEnclosingPosition(pos, with: .character, inDirection: .layout(.left)) else { return false }

        let startIndex = offset(from: beginningOfDocument, to: range.start)

        return attributedText.attribute(.link, at: startIndex, effectiveRange: nil) != nil
    }
     */
    // required to prevent blue background selection from any situation
      override var selectedTextRange: UITextRange? {
          get { return nil }
          set {}
      }

      override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
          if gestureRecognizer is UIPanGestureRecognizer {
              return super.gestureRecognizerShouldBegin(gestureRecognizer)
          }
          if let tapGestureRecognizer = gestureRecognizer as? UITapGestureRecognizer,
              tapGestureRecognizer.numberOfTapsRequired == 1 {
              return super.gestureRecognizerShouldBegin(gestureRecognizer)
          }
          if let longPressGestureRecognizer = gestureRecognizer as? UILongPressGestureRecognizer,
              longPressGestureRecognizer.minimumPressDuration < 0.325 {
              return super.gestureRecognizerShouldBegin(gestureRecognizer)
          }
          gestureRecognizer.isEnabled = false
          return false
      }

}

방법은 2가지다.

 

처음 주석된 point함수를 재정의하여, 사용자가 클릭한 위치를 알아내고 해당 포지션을 기준으로 클릭한 범위를 알아낸다. 클릭한 범위의 시작점을 바탕으로 이미 링크된 영역인지 아닌지를 감별하여 true, false를 리턴한다.

 

참고로 하드카피로 UITextView에 들어가는 text에 링크를 거는 방법의 기본은 기본 text = "abc"가아니라 NSMutableAttributedString타입의 텍스트를 할당해야하고, 이를 바탕으로 개발자 편의를 위해 제공해주는 것이 textView.dataDetectorType과 같은 함수들이다. 그 안을 파고들면 NSMutableAttributedString가 있다는 뜻이다. 그러므로 이 함수를 재 정의하여 링크가 걸려있을 때만 해당 기능이 동작하도록 하는 것이다.

 

두 번재 방법은 텍스트뷰의 기본 변수 selectedTextRange를 재정의하는 건데, 이 변수는 사용자가 텍스트 뷰안에 있는 텍스트를 누르면 해당 범위가 드래그되는 그 것! 그것을 없애주는 것이다. gestureRecognizerShouldBegin은 해당 텍스트뷰에 addGestureRecognizer가 걸려있을 때 호출되는 함수이며, 이것을 보면 dataDetectorType에 링크를 걸었기에, 그 링크 클릭하면 사파리가 켜지는 그 기능 자체에 gestureRecognizer가 걸려있다는 것을 알 수 있다.  그러므로 이 함수를 재정의하여 링크를 클릭, 롱클릭했을 때만 동작하도록 하는 것이다.

 

 

참고

https://stackoverflow.com/questions/36198299/uitextview-disable-selection-allow-links