WebView Control
- 내가 담당한 업무 중에 구글 검색내역과 유튜브 시청내역을 스크래핑 해야했다.
- 유저가 로그인만 하면 뷰를 숨기고 프로그램적으로 사이트를 이동시켜야 했고,
- 앱내의 유저 로그인 데이터가 사라졌을 경우에 대한 예외 처리 까지 해야했다.
- 프로그램적으로 이동한 방법을 공유한다.
Where Shoud I Start To Contorl WebView
func webView(_ webView: WKWebView,
didFinish: WKNavigation!) {
}
- 상기의 펑션은 웹뷰의 랜더링이 끝나면 불리는 펑션이다.
- 상기의 펑션에서 URL을 확인한 후 그 다음 URL을 요청하는 식으로 이동을 진행했다.
if url.absoluteString.contains( GOOGLE_MYACCOUNT_PAGE) {
let myURL = URL(string: YOUTUBE_SEARCH_HISTORY )
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
- 상기의 코드와 같이 구글 어카운트페이지 URL이라면
- 다음 목적지인 유튜브 검색 기록 페이지를 로드해줬다.
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
preferences: WKWebpagePreferences,
decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void){
return decisionHandler(.allow, preferences)
}
- 상기의 펑션은 URL을 로드할 때 권한을 요청할 때 불린다.
- 따라서 이곳에서 URL에 따라 decisionHandler의 값을 주면 특정 URL의 로드를 막을 수 있다.
- 이 곳은 아무래도 웹뷰가 랜더링 되는 곳 보다 펑션이 빨리 불린다.
if let url = webView.url {
if url.absoluteString.contains( GOOGLE_MYACCOUNT_PAGE) {
webView.frame(forAlignmentRect: CGRect(x: 0, y: 0, width: 0, height: 0))
webView.isHidden = true
}
}
- 따라서 상기의 코드처럼 프로그램적으로 UI의 크기를 줄이거나 뷰를 숨기기 좋다
- 유저들이 사이트가 자동으로 이동하는 모습을 보면 아무래도 거부감을 느끼기 때문이다.
import UIKit
import WebKit
class YoutubeScrapingViewController: UIViewController, WKNavigationDelegate {
var youtubeScrapingModel: YoutubeScrapingModel = YoutubeScrapingModel()
fileprivate let GOOGLE_LOGIN_ADDRESS = "https://accounts.google.com/ServiceLogin"
fileprivate let GOOGLE_MYACCOUNT_PAGE = "https://myaccount.google.com/"
fileprivate let GOOGLE_SEARCH_HISTORY = "https://myactivity.google.com/activitycontrols/webandapp?view=item&product=19"
fileprivate let YOUTUBE_SEARCH_HISTORY = "https://myactivity.google.com/activitycontrols/youtube"
fileprivate let GOOGLE_LOGIN_DATA_EXIST = "https://myaccount.google.com/?utm_source=sign_in_no_continue"
var webView: WKWebView!
var loginDetectedAction : () -> Void = {}
override func loadView() {
let preference = WKPreferences()
let webConfiguration = WKWebViewConfiguration()
webConfiguration.preferences = preference
webView = WKWebView(frame: .zero, configuration: webConfiguration)
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let myURL = URL(string: GOOGLE_LOGIN_ADDRESS )
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
func webView(_ webView: WKWebView,
decidePolicyFor navigationAction: WKNavigationAction,
preferences: WKWebpagePreferences,
decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void){
print( "권한요청" )
if let url = webView.url {
if url.absoluteString.contains( GOOGLE_MYACCOUNT_PAGE) {
webView.frame(forAlignmentRect: CGRect(x: 0, y: 0, width: 0, height: 0))
webView.isHidden = true
}
}
return decisionHandler(.allow, preferences)
}
func webView(_ : WKWebView, didCommit: WKNavigation!){
print("수신 시작")
}
func webView(_ webView: WKWebView,
didFinish: WKNavigation!) {
print("탐색이 완료")
print("WebView URL : \(String(describing: webView.url))")
if let url = webView.url {
if url.absoluteString.contains( GOOGLE_MYACCOUNT_PAGE) {
let myURL = URL(string: YOUTUBE_SEARCH_HISTORY )
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
if url.absoluteString.contains( YOUTUBE_SEARCH_HISTORY ) {
let scrollPoint = CGPoint(x: 0, y: webView.scrollView.contentSize.height * 300)
webView.scrollView.setContentOffset(scrollPoint, animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 4.0) {
self.youtubeScrapingModel.insertYoutubeKeywordData(url: url)
self.dismiss(animated: false, completion: nil)
}
}
if url.absoluteString.contains( GOOGLE_LOGIN_ADDRESS ) {
print("LOGIN DETECTED ")
loginDetectedAction()
}
if url.absoluteString.contains( GOOGLE_LOGIN_DATA_EXIST ) {
print("Google Login Data Exist")
}
}
}
func webView(_ webView: WKWebView,
didFailProvisionalNavigation: WKNavigation!,
withError: Error) {
print("초기 탐색 프로세스 중에 오류가 발생했음")
}
func webView(_ webView: WKWebView,
didFail navigation: WKNavigation!,
withError error: Error) {
print("탐색 중에 오류가 발생했음")
}
}
Counclusion
- 스크래핑은 웬만하면 서버에서 했으면 좋겠다.
- 앱에서 직접 스크래핑을 하니 스크래핑 과정을 숨기거나 예외처리를 하거나 동작제어를 하는 게 힘들다.
- SwiftUI와 UIKit 두 곳에서 진행했는데 숨기는 과정은 SwiftUI가 제약사항이 훨씬 많았다.
- Safari의 방어로직이 있어서 스크래핑도 원활이 진행되지 않는다.
- AHIG에 반하는 것들을 가끔 구현하면 내 생물학적 수명이 깎인다.
- 그래도 안정적인 것은 UIKit다 SwiftUI는 AHIG에 반하는 것들을 구현하려고 하면 버그가 난다.