티스토리 뷰
[iOS] URL과 전화번호를 인식하는 Label View 만들기(TextView, dataDetectorTypes, LineBreakStrategy)
jisuuuu 2023. 10. 13. 20:03앱을 개발하다보면 URL이나 전화번호를 연결해줘야 할 경우가 많이 있다. URL은 링크로, 전화번호는 전화로 연결을 해주어야 한다.
그렇다면 링크와 전화번호를 UILabel에서 인식을 해야 하고, 하이라이트 등의 기능을 구현해야 하는데 ..
이 기능을 훌륭하게도 UITextView가 가지고 있다.
이번 포스팅에서는 UITextView를 이용해 링크와 전화번호 등의 인식과 연결을 구현하고,
UILabel 처럼 사용하는 방법을 알아보려고 한다.
1. 샘플 뷰
먼저 스토리보드에서 다음과 같은 뷰를 후다닥 만들어주었다.
동일한 내용의 더미 데이터고, 위는 UILabel, 아래는 UITextView로 만들었다.
앞서 얘기했었던 URL과 전화번호를 인식하는 기능은 UILable 에서는 기본 제공하지 않는다.
UITextView의 attribute Inspector의 Data Detectors에서 확인할 수 있다.
폰넘버, Link 말고도 다양한 옵션들이 있는걸 확인할 수 있다.
먼저 이렇게 빌드를 해보면 다음과 같은 뷰를 확인할 수 있다.
윽 몇가지 문제가 있다.
- DataDetection이 작동하지 않는다.
- UITextView에 leading inset이 있다.
- LineBreak가 다르다
2. dataDetectorTypes
https://developer.apple.com/documentation/uikit/uitextview/1618607-datadetectortypes
dataDetectorTypes | Apple Developer Documentation
The types of data that convert to tappable URLs in the text view.
developer.apple.com
도큐먼트에 따르면 isEditable 이 true로 set 되어 있으면 작동하지 않는다고 한다.
수정한 결과 다음 뷰 처럼 하이라이팅도 되고 safari로 자동으로 연결 되는 것을 확인할 수 있다.
3. Layout이 다르다.
레이아웃이 다른 부분을 확인해보기 위해 ViewHierarchy Debug를 열어서 확인해보자
아하 일단 그냥 보기에도 Label과는 뭔가 많이 다르다.
Label은 하나의 컴포넌트가 통짜로 만들어져 있지만
UITextView는 내부에 비교적 많은 뷰들이 있고, 문단별로도 fragment로 쪼개져 있는 것을 볼 수 있다.
이는 UITextView가 UILabel에 비해 더 많은 기능이 들어있기 때문이다.
UILabel은 단순히 텍스트를 표현하는 역할이라면,
UITextView에서는 사용자의 입력을 받는 것, 스크롤, 영역선택 및 우리가 사용하려는 DataDetection 등의 기능을 포함하기 때문이다.
결론적으로 UITextView 내부에는 크게 UITextContainerView - UITextLayoutFragmentView 로 구성이 되어있고
그리고 우리가 확인했던 텍스트의 inset은 UITextLayoutFragmentView에 적용이 되어 있는 것을 확인할 수 있다.
우리가 확인한 것 처럼 UITextView 내부에는 textContainer가 있고, 그 내부에는 lineFragmentPadding 이라는 속성값이 있다.
현재 값을 확인해보니 CGFloat 5.0 으로 기본값이 설정되어 있다. 이 값을 0으로 변경해주니 padding이 사라진 것을 볼 수 있다.
4. LineBreakMode가 다르다
self.contentTextView.textContainer.lineBreakMode = .byWordWrapping
위 코드로 lineBreakMode를 word로 설정해줬는데 적용이 되지 않는다. 무엇이 문제일까?
일단 TextContainerView 내부의 text 속성값은 UILabel과 UITextView이 동일한 속성값을 사용할 것으로 생각을 했다.
그리고 AttributeText에 차이가 있을 것으로 생각해 속성을 확인해보았다.
설정한대로 LineBreakMode는 동일하게 WordWrapping으로 설정되어 있는데 LineBreakStrategy는 65535이라는 엄청난(?) 값의 차이가 있었다.
LineBreakStrategy의 도큐먼트를 확인해보자
NSParagraphStyle 내부의 LineBreakStrategy에는 무려 hangulWordPriority 가 있다.
hangul .. hangul ? 내가 알고 있는 그 한글이 맞는건가.. ?
The Text system prohibits breaking between Hangul characters
-> 한글 한글자마다 breaking 되는 것을 막는다
일단 우리에게 필요한 옵션인 것 같다. 바로 적용해보자.
// Attribute 설정
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.lineBreakStrategy = .hangulWordPriority
let attributeString = NSAttributedString(string: self.content, attributes: [.paragraphStyle: paragraphStyle, .font: UIFont.systemFont(ofSize: 15, weight: .regular)])
self.contentTextView.attributedText = attributeString
lineBreakStrategy를 적용한 paragraphStyle을 만들고, attributeString으로 적용한 뒤 textView에 넣어주었다.
(기존의 text attribute가 초기화 되므로 함께 다시 설정해준다.)
빌드해서 확인해보니 UILabel과 동일한 레이아웃을 가진 textView가 되었다.
5. 결론
앞에서 얘기한 hangulWordPriority는 iOS14부터 적용이 가능하다.
OS 분기처리가 필요한 경우가 있을 것 같다.
나는 이러한 세팅을 Extension으로 저장해 사용한다.
아래 코드는 content와 font를 파라미터로 받지만, 경우에 따라 다른 속성값들도 파라미터로 받으면 확장성도 높아지고 편리하게 사용할 수 있을 것이다.
extension UITextView {
func makeLookLikeLabel(content: String, font: UIFont) {
// TextView 설정
self.isEditable = false
self.isScrollEnabled = false
self.dataDetectorTypes = [.link, .phoneNumber]
self.textContainer.lineFragmentPadding = 0
self.textContainer.lineBreakMode = .byWordWrapping
// Attribute 설정
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineBreakMode = .byWordWrapping
paragraphStyle.lineBreakStrategy = .hangulWordPriority
let attributeString = NSAttributedString(string: content, attributes: [.paragraphStyle: paragraphStyle, .font: font])
self.attributedText = attributeString
}
}
또한, 위에서 보다시피 UILabel에 비해 UITextView는 많은 기능이 들어가 있어 우리가 필요한 기능을 구현할 수 있었다 (dataDetection)
하지만 그에 따라 더 많은 성능이 필요하다는 것을 알고 사용해야 할 것이다 !
예제코드: https://github.com/jisu15-kim/examples/tree/main/TextViewExample
'iOS' 카테고리의 다른 글
[iOS] FLO 앱 만들기(2) - 재사용을 고려한 플레이어 버튼 만들기 (0) | 2023.10.14 |
---|---|
[iOS] FLO 앱 만들기(1) - AVFoundation 개요 / 앱 구조 설계 (1) | 2023.10.14 |
[iOS] 함수형 프로그래밍을 클로저로 적용한 간단한 UI 만들어보기 (0) | 2023.10.13 |
[iOS] Push Notification으로 화면 전환하기 (0) | 2023.10.13 |
[iOS] 커스텀 팝업 선택 뷰 만들기 (0) | 2023.10.13 |
- Total
- Today
- Yesterday
- KVO
- openapi-generator
- IOS
- watchOS
- retry
- SwiftUI
- easy cue
- open-api-generator
- AVFoundation
- TextField
- flo
- OAS
- avplayer
- 2024년
- keyboardtype
- locale
- DateFormatter
- auth
- swift날짜
- 애플워치
- 소수점
- musicplayer
- Xcode15
- watch connectivity
- Swift
- 애플워치 데이터 전송
- 회고
- Xcode
- demical
- 토큰
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |