FireBase - UITextView with open WKWebView
import UIKit
class RegisterController: UIViewController {
...
private let termsTextView: UITextView = {
let textView = UITextView()
let attributedString = NSMutableAttributedString(string: "By creating an account, you agree to our Terms & Conditions and you acknowledge that you have read our Privacy Policy.")
attributedString.addAttribute(.link, value: "terms://termsAndConditions", range: (attributedString.string as NSString).range(of: "Terms & Conditions"))
attributedString.addAttribute(.link, value: "privacy://privacyPolicy", range: (attributedString.string as NSString).range(of: "Privacy Policy"))
textView.linkTextAttributes = [.foregroundColor: UIColor.systemBlue]
textView.backgroundColor = .clear
textView.attributedText = attributedString
textView.textColor = .label
textView.isSelectable = true
textView.isEditable = false
textView.delaysContentTouches = false
textView.isScrollEnabled = false
return textView
}()
// MARK: - Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
self.termsTextView.delegate = self
...
}
// MARK: Extensions
extension RegisterController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if URL.scheme == "terms" {
self.showWebViewerController(with: "https://policies.google.com/terms?h1=en")
} else if URL.scheme == "privacy" {
self.showWebViewerController(with: "https://policies.google.com/privacy?h1=en")
}
return true
}
private func showWebViewerController(with urlString: String) {
let vc = WebViewerController(with: urlString)
let nav = UINavigationController(rootViewController: vc)
self.present(nav, animated: true, completion: nil)
}
func textViewDidChangeSelection(_ textView: UITextView) {
textView.delegate = nil
textView.selectedTextRange = nil
textView.delegate = self
}
}
termsTextView와 관련된 코드 설명
1. termsTextView의 역할
- termsTextView는 사용자가 회원가입을 진행하면서 약관 동의 및 개인정보 처리방침을 확인할 수 있도록 안내하는 텍스트 뷰입니다.
- UITextView를 사용하여 링크를 포함한 약관 내용을 표시하며, 사용자가 특정 텍스트(예: "Terms & Conditions", "Privacy Policy")를 클릭하면 해당 링크로 이동합니다.
2. termsTextView 초기화
private let termsTextView: UITextView = {
let textView = UITextView()
let attributedString = NSMutableAttributedString(string: "By creating an account, you agree to our Terms & Conditions and you acknowledge that you have read our Privacy Policy.")
attributedString.addAttribute(.link, value: "terms://termsAndConditions", range: (attributedString.string as NSString).range(of: "Terms & Conditions"))
attributedString.addAttribute(.link, value: "privacy://privacyPolicy", range: (attributedString.string as NSString).range(of: "Privacy Policy"))
textView.linkTextAttributes = [.foregroundColor: UIColor.systemBlue]
textView.backgroundColor = .clear
textView.attributedText = attributedString
textView.textColor = .label
textView.isSelectable = true
textView.isEditable = false
textView.delaysContentTouches = false
textView.isScrollEnabled = false
return textView
}()
주요 설정
- 텍스트와 스타일 설정:
- 기본 텍스트: 약관 동의를 설명하는 문자열을 생성.
- 하이퍼링크 설정:
- NSMutableAttributedString을 사용해 "Terms & Conditions"와 "Privacy Policy"에 링크를 추가.
- 링크는 커스텀 URL 스키마(예: terms:// 또는 privacy://)로 정의.
- 텍스트 스타일:
- 링크 색상: 파란색 (systemBlue).
- 배경색: 투명 (clear).
- 텍스트 색상: 시스템 라벨 색상(label)로 설정해 다크/라이트 모드 호환.
- 사용자 상호작용 설정:
- isSelectable = true: 사용자가 텍스트를 클릭할 수 있도록 설정.
- isEditable = false: 텍스트 편집 불가.
- isScrollEnabled = false: 텍스트가 자동으로 스크롤되지 않도록 설정.
UITextViewDelegate와 extension
UITextViewDelegate를 통한 하이퍼링크 처리
termsTextView가 하이퍼링크를 지원하기 위해 UITextViewDelegate를 구현합니다.
1.shouldInteractWith URL:
- 사용자가 termsTextView의 링크를 클릭했을 때 호출됩니다.
- URL 스키마(scheme)에 따라 적절한 동작을 수행.
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if URL.scheme == "terms" {
self.showWebViewerController(with: "https://policies.google.com/terms?h1=en")
} else if URL.scheme == "privacy" {
self.showWebViewerController(with: "https://policies.google.com/privacy?h1=en")
}
return true
}
작동 방식
- URL 스키마 검사:
- "terms": "Terms & Conditions" 링크 클릭 시 Google 약관 페이지로 이동.
- "privacy": "Privacy Policy" 링크 클릭 시 Google 개인정보 처리방침 페이지로 이동.
- showWebViewerController 호출:
- WebViewerController를 생성해 URL 콘텐츠를 표시.
2. showWebViewerController(with urlString: String)
private func showWebViewerController(with urlString: String) {
let vc = WebViewerController(with: urlString)
let nav = UINavigationController(rootViewController: vc)
self.present(nav, animated: true, completion: nil)
}
역할
- 전달받은 urlString을 이용해 WebViewerController를 생성.
- UINavigationController를 감싸 모달로 표시.
3.textViewDidChangeSelection:
func textViewDidChangeSelection(_ textView: UITextView) {
textView.delegate = nil
textView.selectedTextRange = nil
textView.delegate = self
}
역할
- 텍스트 뷰에서 텍스트 선택 동작을 무효화.
- 사용자가 텍스트를 드래그하거나 선택하려고 시도하면 이를 방지하고 초기 상태로 복원.
코드 동작 흐름
- termsTextView에 약관 설명과 링크가 포함된 텍스트를 설정.
- 사용자가 "Terms & Conditions" 또는 "Privacy Policy"를 클릭:
- UITextViewDelegate의 shouldInteractWith URL 호출.
- 링크의 scheme에 따라 적절한 URL로 WebViewerController를 표시.
- 링크 클릭 외 텍스트 선택 동작이 발생하면 textViewDidChangeSelection으로 이를 무효화
🔥 URL 스키마(URL Scheme)를 사용하는 것과 직접 URL 주소를 입력하는 것의 차이를 이해하기 위해, 두 방식의 개념과 장단점을 비교해 보겠습니다.
1. URL 스키마란?
- URL 스키마는 앱에서 특정 동작을 트리거하기 위한 사용자 정의 프로토콜입니다.
- 일반적인 URL(https://example.com) 대신, 커스텀 스키마를 정의합니다(예: terms://termsAndConditions).
- 이 방식은 URL을 입력했을 때 특정한 처리를 수행하도록 맞춤 설정할 수 있습니다.
2. URL 스키마와 직접 URL의 차이점
특징 | URL 스키마 (terms://) | 직접 URL (https://) |
사용 목적 | 앱 내부 로직을 처리하거나 특정 동작을 트리거 | 외부 리소스(웹 페이지, API 등)로 연결 |
유연성 | 동작을 앱에서 커스터마이즈 가능 | URL에 접근하면 항상 동일한 리소스 반환 |
URL 처리 방식 | 커스텀 처리가 필요 (UITextViewDelegate 등 사용) | URL을 웹 브라우저나 WebView로 직접 로드 가능 |
외부 서비스 연동 | 외부 웹 리소스와는 직접 연관되지 않음 | 외부 서비스에서 URL을 처리 가능 |
사용 사례 | 앱 내 특정 화면 이동, 비즈니스 로직 트리거 | 웹 페이지 로드, 외부 리소스 연결 |
3. URL 스키마의 필요성
1) 앱 내부 동작과 로직 처리
- URL 스키마는 특정 URL에 반응하여 앱의 특정 로직을 실행할 수 있습니다.
- 예를 들어:
- terms://termsAndConditions → 약관 보기 화면 표시.
- privacy://privacyPolicy → 개인정보 처리방침 화면 표시.
- 이렇게 하면 URL 입력으로 앱 내의 다양한 동작을 효율적으로 구현할 수 있습니다.
2) 외부 URL에 종속되지 않음
- URL 스키마를 사용하면 앱이 특정 동작을 실행하기 위해 외부 URL의 변경 여부에 영향을 받지 않습니다.
- 예: 약관 URL이 변경되더라도 앱 내부의 스키마를 유지하면 로직을 변경할 필요가 없습니다.
3) 재사용성과 유지보수성 증가
- URL 스키마를 통해 공통된 동작을 중앙에서 관리할 수 있습니다.
- 앱 내부에서 처리해야 하는 동작은 스키마만 확인하면 쉽게 처리 가능하며, 유지보수가 용이합니다.
4) 복잡한 로직과의 통합
- URL 스키마는 단순히 웹 페이지를 로드하는 것 이상으로 앱 내부 데이터 처리 또는 비즈니스 로직과 결합할 수 있습니다.
- 예: app://product/123 → 특정 상품 화면으로 이동.
- 이와 같은 방식으로 특정 데이터를 동적으로 전달받아 처리할 수도 있습니다.
4. 직접 URL 입력의 한계
- 직접 URL을 입력하면 항상 해당 URL로 연결되기 때문에 고정된 동작만 수행합니다.
- 예를 들어, https://example.com/terms를 입력하면 약관 페이지가 로드되지만, 앱 내에서 다른 처리를 할 수 있는 유연성이 부족합니다.
5. URL 스키마를 사용하는 실제 사례
- 앱 내 네비게이션
- 특정 URL 스키마를 사용하여 앱 내부의 다른 화면으로 이동.
- 예: myapp://settings → 앱 설정 화면 표시.
- 특정 이벤트 트리거
- URL 스키마로 특정 이벤트를 트리거할 수 있음.
- 예: myapp://loginSuccess → 로그인이 성공한 후 동작 실행.
- 웹과 앱 간의 통합
- 웹 페이지에서 앱으로 특정 동작을 트리거할 때 사용.
- 예: 앱 다운로드 링크에서 myapp://open을 사용해 앱 실행.
- 다양한 데이터 처리
- URL 스키마에 데이터를 포함시켜 앱에서 이를 처리 가능.
- 예: myapp://product?id=123 → 특정 상품 상세 화면 표시.
결론
URL 스키마를 사용하는 이유:
- 앱 내부에서 동작을 커스터마이즈할 수 있습니다.
- 외부 URL에 의존하지 않고 앱의 동작을 제어합니다.
- 특정 URL에 따른 복잡한 로직 처리 및 데이터 전달이 가능합니다.
직접 URL을 사용하는 경우:
- 단순히 웹 페이지를 열거나 외부 리소스에 연결할 때 사용됩니다.
따라서, URL 스키마는 앱 내부 동작을 효율적으로 처리하거나, 동작을 커스터마이즈하고 외부 리소스와 독립적인 로직을 유지하고자 할 때 매우 유용합니다.
🔥 그럼 "terms://termsAndConditions", "privacy://privacyPolicy"을 사용한 이유?
위 코드에서 URL 스키마를 설정한 이유는 링크 클릭 시 앱 내부에서 커스텀 로직을 처리하기 위해서입니다. 단순히 하이퍼링크를 바로 걸지 않은 이유는 특정 조건에 따라 링크를 처리하거나, 사용자가 URL을 클릭했을 때 앱이 원하는 방식으로 동작하도록 만들기 위함입니다.
URL 스키마를 설정한 이유
1. 앱 내에서 동작을 제어하기 위해
- terms://termsAndConditions와 privacy://privacyPolicy를 사용하면 **하나의 처리 로직(Delegate 메서드)**으로 클릭 이벤트를 감지하여, 조건에 따라 원하는 작업을 실행할 수 있습니다.
- 예를 들어, 사용자가 클릭한 링크가 "Terms & Conditions"인지, "Privacy Policy"인지 확인하고 각 링크에 따라 다른 동작을 수행할 수 있습니다.
코드에서 이를 처리하는 부분:
func textView(_ textView: UITextView, shouldInteractWith URL: URL, in characterRange: NSRange) -> Bool {
if URL.scheme == "terms" {
self.showWebViewerController(with: "https://policies.google.com/terms?h1=en")
} else if URL.scheme == "privacy" {
self.showWebViewerController(with: "https://policies.google.com/privacy?h1=en")
}
return true
}
여기서의 장점:
- 클릭한 URL의 **스키마(terms 또는 privacy)**를 검사하고, 필요한 경우 다른 URL로 리디렉션하거나 추가 작업을 수행할 수 있음.
- 예를 들어:
- terms://를 클릭하면 약관 페이지를 앱 내의 뷰어로 표시.
- 특정 링크 클릭 시 사용자 데이터를 로깅하거나 통계를 수집.
2. 유연성 증가
- 직접 URL을 넣을 경우, "Terms & Conditions"와 "Privacy Policy"를 각각 특정 URL로 연결하게 됩니다. 이후 약관이나 정책 URL이 변경되면 모든 URL을 수정해야 합니다.
- 반면 URL 스키마를 사용하면:
- 스키마 자체는 고정 (terms://termsAndConditions).
- URL 매핑을 코드 내부에서 관리할 수 있어 유지보수가 더 쉬워집니다.
예를 들어:
- 현재 terms://termsAndConditions가 https://policies.google.com/terms?h1=en으로 연결되어 있지만, URL이 변경되면 아래와 같은 코드만 수정하면 됩니다:
if URL.scheme == "terms" {
self.showWebViewerController(with: "https://new-terms-url.com")
}
왜 하이퍼링크를 바로 걸지 않았을까?
하이퍼링크를 바로 걸었다면:
let attributedString = NSMutableAttributedString(string: "By creating an account, you agree to our Terms & Conditions and you acknowledge that you have read our Privacy Policy.")
attributedString.addAttribute(.link, value: "https://policies.google.com/terms?h1=en", range: (attributedString.string as NSString).range(of: "Terms & Conditions"))
attributedString.addAttribute(.link, value: "https://policies.google.com/privacy?h1=en", range: (attributedString.string as NSString).range(of: "Privacy Policy"))
이렇게 하면 링크를 클릭했을 때 해당 웹 페이지가 열리는 데 그치며, 추가 로직이나 동작을 제어할 방법이 없습니다.
결론
URL 스키마를 설정한 이유는 단순히 링크를 여는 것 이상으로, 앱 내에서 클릭 이벤트를 세분화하고, 유지보수성과 유연성을 높이기 위함입니다.
특히, 앱 내부에서 로직을 제어하고 싶거나 약관 및 정책과 같은 변경 가능성이 있는 리소스를 처리할 때 URL 스키마는 매우 효과적인 방법입니다.
🔥 value: "terms://termsAndConditions"와 URL.scheme == "terms" 사이의 관계는 URL의 구조에 따라 동작합니다. 이를 이해하려면 URL의 구성 요소와 URL.scheme의 역할을 살펴보아야 합니다.🔥
1. URL의 구조
URL은 아래와 같은 구조로 이루어져 있습니다:
<scheme>://<host>/<path>?<query>#<fragment>
부분의 의미:
- scheme: URL의 프로토콜 또는 스키마를 나타냅니다. (예: https, http, terms 등)
- host: 도메인 또는 리소스 위치. (예: www.example.com)
- path: 호스트 내부에서 특정 리소스의 경로. (예: /termsAndConditions)
- query: 추가적인 매개변수를 전달하기 위한 쿼리 문자열. (예: ?id=123)
- fragment: 페이지 내 특정 위치를 나타내는 부분. (예: #section1)
2. 코드에서 사용된 URL
value: "terms://termsAndConditions"
이 URL은 다음과 같이 분해할 수 있습니다:
- scheme: "terms"
- host: 없음 (명시되지 않음)
- path: "/termsAndConditions"
3. URL.scheme의 역할
URL.scheme
- URL 객체의 scheme 프로퍼티는 URL의 스키마 부분만 반환합니다.
let url = URL(string: "terms://termsAndConditions")
print(url?.scheme) // 출력: "terms"
4. 코드 동작 설명
URL의 value 설정:
attributedString.addAttribute(.link, value: "terms://termsAndConditions", range: ...)
- value로 설정된 URL은 terms://termsAndConditions입니다.
- 사용자가 링크를 클릭하면 UITextViewDelegate의 shouldInteractWith 메서드로 전달되는 URL 객체에 이 값이 포함됩니다.
URL의 스키마 확인:
if URL.scheme == "terms" {
self.showWebViewerController(with: "https://policies.google.com/terms?h1=en")
}
- URL.scheme는 위에서 설정된 "terms://termsAndConditions" URL의 스키마(terms)를 반환합니다.
- 따라서, "terms"와 일치하는지 확인하여 적절한 동작을 수행합니다.
6. 요약
- value: "terms://termsAndConditions"의 의미:
- URL 전체를 설정하며, scheme은 "terms"이고 path는 "/termsAndConditions"입니다.
- URL.scheme:
- URL에서 스키마 부분(즉, terms)만 추출합니다.
- 왜 스키마를 사용했는가:
- 스키마를 통해 URL을 분류하고, 적절한 동작을 유연하게 처리할 수 있습니