[iOS] - 댓글 내 언급 기능 구현하기

sun02·2023년 2월 8일
0

iOS

목록 보기
23/27

최근에 위와 같이 멘션을 포함한 대댓글을 구현해야했는데
어떻게 해야할 지 전혀 감을 못잡았었기에 복습 및 공유를 위해 작성합니다 ..😅

목차

  1. 시도 했던 아이디어
  2. 구현 방법
  3. 구현 시 주의할 점
  4. 그 외

1. 시도 했던 아이디어

- stackView로 아이디 버튼과 댓글 라벨 구현하기

  • 댓글이 길어지면 두번째 줄부턴 아이디 버튼 아래로 leading이 바뀌어야하는데 레이아웃이 그렇게는 안되더라구요. ChatGPT한테까지 물어보았는데.. 😰

- 아이디 버튼의 width만큼 label의 leading margin 설정하기

이 방법을 시도하려다 갑자기 깨달았습니다 사용자는 아이디 언급을 댓글 상단, 중간, 하단 어디서나 할 수 이있다는 것을..(아..🌟

  • 즉, 아이디버튼과 댓글은 한 컴포넌트 내에 존재해야합니다

그래서 어떤 방법이 있을 지 서치를 하다가 해시태그 구현에 대한 포스팅을 보게 되었습니다.

그래서 오! 그렇다면 언급도 해시태그처럼 구현하면 되지 않을까? 라고 생각하며 적용해보았습니다.


2. 구현 방법

link란?
text에 적용하는 NSAttributed.key 의 속성 중 하나로 text에 NSURL 값을 넣을 수 있고 해당 text를 누르면 해당 URL을 전달 받을 수 있음

따라서, 이 link 속성을 이용하여 해당 text(언급된 아이디)를 버튼처럼 clickable하게 만들어준다면 우리가 원하는 댓글 내 언급 기능을 구현할 수 있습니다. 🌟


- 코드로 구현하기

이제 이 link속성을 가진 TextView(MentionTextView)를 만들어 보겠습니다.

- MentionTextView

import UIKit

/// 멘션된 아이디를 clickable하게 바꿔주는 텍스트 뷰
/// 한글, 영문, 숫자만 가능
class MentionTextView: UITextView {
    var idArray: [String] = []
    
    func findOutMetionedId() {
        self.isEditable = false
        self.isSelectable = true
        self.isScrollEnabled = false
        
        /// mentionTextView의 텍스트에 기본으로 적용되는 설정 값
        let attributes: [NSAttributedString.Key: Any] = [
            .foregroundColor: UIColor.black,
            .font: UIFont.systemFont(ofSize: 14.0, weight: .regular),
          ]
        
        let nsText: NSString = self.text as NSString
        let attrString = NSMutableAttributedString(string: nsText as String, attributes: attributes)
        let mentionDetector = try? NSRegularExpression(pattern: "@(\\w+)", options: NSRegularExpression.Options.caseInsensitive)
        let results = mentionDetector?.matches(in: self.text,
                                               options: NSRegularExpression.MatchingOptions.withoutAnchoringBounds,
                                               range: NSMakeRange(0, self.text.utf16.count))

        guard let results = results else { return }
        idArray = results.map{ (self.text as NSString).substring(with: $0.range(at: 1)) }
                                
        if !idArray.isEmpty {
            var i = 0
            for var word in idArray {
                word = "@" + word
                if word.hasPrefix("@") {
                    let matchRange:NSRange = nsText.range(of: word as String, options: [.caseInsensitive, .backwards])
                    
                    /// @로 시작하는 텍스트인 경우의 속성
                    let linkAttributes: [NSAttributedString.Key: Any] = [
                        .foregroundColor: UIColor(named: "Primary") ?? .black,
                        .font: UIFont.systemFont(ofSize: 14.0, weight: .regular),
                        .link: "\(i)",
                      ]
                    attrString.setAttributes(linkAttributes, range: matchRange)
                    i += 1
                }
            }
        }

        self.attributedText = attrString
    }
}

여기서 주의해서 설정해야하는 건 linkAttributes 입니다.

  • foregroundColor: 언급된 아이디 텍스트 색상
  • font: 언급된 아이디 텍스트 폰트
  • link: 언급된 아이디 텍스트를 link(clickable)하게 변경해주고 해당 텍스트에 값("(i)") 설정
    • 언급된 아이디가 많을 수도 있어서, idArray에서 해당 아이디의 index값을 넣어주었습니다

- MentionTextView를 포함하는 View

이제 mentionTextView내에서 언급된 아이디를 눌렀을 때 해당 아이디를 print하는 action을 구현해보겠습니다.


- func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction)

textView 내의 url과 사용자 간의 상호작용을 허용할 것인지 묻는 메서드

extension CommentView: UITextViewDelegate {
    
    func textView(_ textView: UITextView, shouldInteractWith url: URL, in characterRange: NSRange, interaction: UITextItemInteraction) -> Bool {
        
        if let mentionTextView = textView as? MentionTextView,
           let text = Int(url.debugDescription) {
            print(mentionTextView.idArray[text])
            return true
        }
        return false
    }
}

따라서 url 값을 가지고 있는 clickable 한 id를 눌렀을 때 호출됩니다.

  • url: link 속성에서 설정되는 url
    • 원래는 URL타입이 들어와야하지만 mentionView에서 link 속성에 idArray의 index값을 설정했기 때문에 url 타입이 아닌 String 타입으로 받아야합니다.
    • ex) url.desription / url.debugDescription / url.absoluteString
    • 이렇게 하지 않으면 넣어준 값이 URL 타입이 아니기 때문에 에러가 납니다
  • mentionTextView의 프로퍼티인 idArray에 받은 text(=index)를 넣어주면 해당 아이디가 출력됩니다!

3. mentionTextView 적용시 주의할 점 🔥!

- textView.delegate = self

아이디 print action이 UITextViewDelegate 메서드에서 수행되기 때문에 반드시 delegate 를 설정해주어야합니다.

- findOutMetionedId() 호출 시점

textView에 텍스트를 넣어주고난 후에 findOutMetionedId() 호출해야합니다.

  • 당연하지만,, 순서가 바뀌면 아이디에 Link 속성이 적용되지 않습니다.

- textView의 text 속성 변경

textView의 text 속성은 외부에서 변경해도 적용되지 않음

  • findOutMetionedId()에서 attributedText 를 설정하고 있기 때문에 textView의 text 속성을 변경해도 적용되지 않습니다.
  • 따로 설정하고 싶다면 findOutMetionedId()에서 변경하셔야합니다.

4. 그 외

- NSAttributed.key 속성이면 Label은 안되나요??

Label도 되긴 했는데...

그러나 custom에 제약이 있었습니다. 모든 속성을 테스트해본건 아니지만, .foregroundColor는 확실히 적용되지 않았습니다 🥹

저는 프로젝트의 Primary color로 언급된 아이디 색상도 바뀌어야했기에 textView로 구현하였으나
만약 default blue로 구현하실 예정이시라면 Label로 구현하셔도 괜찮을 것 같습니다.

0개의 댓글