import Cocoa import WebKit protocol AdsWebViewDelegate: AnyObject { func kmAdViewClicked(_ adView: KMAdsWebView) func kmAdViewClose(_ adView: KMAdsWebView) } enum KMADViewDirections: Int { case up case down } // Banner Ads Width,Height let kAD_View_Width = 728.0 let kAD_View_Height = 90.0 // Banner Ads refresh rate in second, 新广告会自动刷新,所以时间延长 let kAD_Refresh_Rate = 6000.0 class KMAdsWebView: NSView, WKNavigationDelegate, CAAnimationDelegate { weak var adDelegate: AdsWebViewDelegate? private var closeButton: NSButton private var timer: Timer? private var currentPage: Int = 0 private var completionHandler: ((Int) -> Void)? private var adsWebView: WKWebView var adPosY: CGFloat = 30.0 override init(frame frameRect: NSRect) { closeButton = NSButton(frame: NSRect(x: frameRect.size.width - 30, y: frameRect.size.height - 30, width: 30, height: 30)) adsWebView = WKWebView(frame: frameRect) super.init(frame: frameRect) wantsLayer = true self.frame = NSRect(x: 0, y: 0, width: kAD_View_Width, height: kAD_View_Height) autoresizingMask = [.minXMargin, .maxXMargin] adsWebView.navigationDelegate = self adsWebView.autoresizingMask = [.width, .height] addSubview(adsWebView) adPosY = 30.0 closeButton.isHidden = false closeButton.imagePosition = .imageOnly // closeButton.cell?.highlightsBy = .contentsCellMask closeButton.imageScaling = .scaleProportionallyUpOrDown closeButton.image = NSImage(named: "ad_cancel_button00") closeButton.isBordered = false closeButton.target = self closeButton.action = #selector(buttonItemClicked_Close(_:)) addSubview(closeButton) NotificationCenter.default.addObserver(self, selector: #selector(handleUserHaveClickRateUsNotification(_:)), name: NSNotification.Name("kUserHaveClickRateUsNotification"), object: nil) NotificationCenter.default.addObserver(self, selector: #selector(adsWebView_Switch), name: NSNotification.Name("KMFirebaseRemateConfigRequestIsSuccessful"), object: nil) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } deinit { adDelegate = nil adsWebView.navigationDelegate = nil adsWebView.stopLoading() timer?.invalidate() timer = nil completionHandler = nil NotificationCenter.default.removeObserver(self) } var adsData: [String] { get { var data: [String] = [] #if VERSION_DMG if !UserDefaults.standard.bool(forKey: "kUserHaveClickRateUsKey") { data = ["https://test.sitemaji.com/native/pdfreaderpro.html?s=PDFReaderPro_Mac_DMG_HouseAD_728x90"] } else { data = ["https://test.sitemaji.com/native/pdfreaderpro.html?s=PDFReaderPro_Mac_DMG_728x90"] } #else if !UserDefaults.standard.bool(forKey: "kUserHaveClickRateUsKey") { data = ["https://test.sitemaji.com/native/pdfreaderpro.html?s=PDFReaderPro_Mac_Store_HouseAD_728x90"] } else { data = ["https://test.sitemaji.com/native/pdfreaderpro.html?s=PDFReaderPro_Mac_Store_728x90"] } #endif if !UserDefaults.standard.bool(forKey: "kUserHaveClickRateUsKey") { data = [KMKdanRemoteConfig.remoteConfig().displayHouseAdsUrl()] } else { data = [KMKdanRemoteConfig.remoteConfig().displayAdsUrl()] } #if DEBUG #if VERSION_DMG data = ["http://test-pdf-pro.kdan.cn:3021/native?s=PDFReaderPro_Mac_DMG_HouseAD_728x90&testflag=on"] #else data = ["https://test.sitemaji.com/native/pdfreaderpro.html?s=PDFReaderPro_Mac_Store_728x90"] #endif #endif return data } set { } } func restoreDefaultAdPosY() { adPosY = 30.0 } func sortAdsData() { return let tLength = self.adsData.count for _ in 0..<24 { let tIndex0 = Int(arc4random()) % tLength var tIndex1 = Int(arc4random()) % tLength if tIndex0 == tIndex1 { tIndex1 = (tIndex1 + 1) % tLength } self.adsData.swapAt(tIndex0, tIndex1) } } func startAdsDataRequest() { adsWebView.navigationDelegate = self if adsData.count > 0 { let url = URL(string: adsData[currentPage]) adsWebView.load(URLRequest(url: url!)) KMAdsManager.defaultManager.refreshLoadingDate() } } func stopLoading() { adsWebView.navigationDelegate = nil adsWebView.stopLoading() } func resizeWithOldSuperviewSize(oldSize: NSSize) { var width = kAD_View_Width var height = kAD_View_Height if superview?.frame.size.width ?? 0 < width { let newWidth = (superview?.frame.size.width ?? 0) - 20 height = height / width * newWidth width = newWidth } self.frame = NSRect(x: (superview?.frame.size.width ?? 0 - width) / 2.0, y: frame.origin.y, width: width, height: height) adsWebView.frame = bounds } func beginSheetModalForView(view: NSView, directions: KMADViewDirections, animated: Bool, completionHandler handler: ((Int) -> Void)?) { self.completionHandler = handler if adPosY < 0 { restoreDefaultAdPosY() } let tWidth = frame.size.width let tHeight = frame.size.height view.addSubview(self) if animated { switch directions { case .up: frame = NSRect(x: (view.frame.size.width - tWidth) / 2.0, y: view.frame.size.height, width: tWidth, height: tHeight) animator().frame = NSRect(x: frame.origin.x, y: view.frame.size.height - tHeight - adPosY, width: tWidth, height: tHeight) case .down: frame = NSRect(x: (view.frame.size.width - tWidth) / 2.0, y: -tHeight, width: tWidth, height: tHeight) animator().frame = NSRect(x: frame.origin.x, y: adPosY, width: tWidth, height: tHeight) default: frame = NSRect(x: 0, y: 0, width: tWidth, height: tHeight) } } else { switch directions { case .up: frame = NSRect(x: (view.frame.size.width - tWidth) / 2.0, y: view.frame.size.height - tHeight - adPosY, width: tWidth, height: tHeight) case .down: frame = NSRect(x: (view.frame.size.width - tWidth) / 2.0, y: adPosY, width: tWidth, height: tHeight) default: frame = NSRect(x: 0, y: 0, width: tWidth, height: tHeight) } } adsWebView.frame = bounds currentPage = 0 startAdsDataRequest() if timer != nil { timer?.invalidate() timer = nil } var interval = 0 if !UserDefaults.standard.bool(forKey: "kUserHaveClickRateUsKey") { interval = KMKdanRemoteConfig.remoteConfig().refreshAdsDate() } else { interval = KMKdanRemoteConfig.remoteConfig().refreshAdsDateEvaluateAfter() } timer = Timer.scheduledTimer(timeInterval: TimeInterval(interval), target: self, selector: #selector(adsWebView_Switch), userInfo: nil, repeats: true ) RunLoop.current.add(timer!, forMode: .common) } @objc func adsWebView_Switch() { DispatchQueue.main.async { if !KMKdanRemoteConfig.remoteConfig().isDisplayAds() || !KMAdsManager.defaultManager.checkTheDate() || !KMAdsManager.defaultManager.isValidLastShowAds() { self.removeFromSuperview() return } let transition = CATransition() transition.type = .moveIn let tRandomData = arc4random() % 100 if tRandomData > 50 { transition.subtype = .fromBottom } else { transition.subtype = .fromTop } transition.duration = 0.6 transition.delegate = self self.layer?.add(transition, forKey: "KMTransitionAnimation") self.currentPage += 1 if self.currentPage >= self.adsData.count { self.currentPage = 0 } let url = URL(string: self.adsData[self.currentPage]) self.adsWebView.load(URLRequest(url: url!)) KMAdsManager.defaultManager.refreshLoadingDate() } } @objc func buttonItemClicked_Open(_ sender: Any) { guard let newURL = (sender as? NSURL) else { return } NSWorkspace.shared.open(newURL as URL) adDelegate?.kmAdViewClicked(self) if let completionHandler = self.completionHandler { completionHandler(currentPage + 1) self.completionHandler = nil } removeFromSuperview() } @objc func buttonItemClicked_Close(_ sender: Any) { adDelegate?.kmAdViewClose(self) if let completionHandler = self.completionHandler { completionHandler(0) self.completionHandler = nil } removeFromSuperview() } func animationDidStart(_ anim: CAAnimation) { if timer != nil { timer?.invalidate() } closeButton.alphaValue = 0 } func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if !flag { return } if timer != nil { timer?.invalidate() } var interval = 0 if !UserDefaults.standard.bool(forKey: "kUserHaveClickRateUsKey") { interval = KMKdanRemoteConfig.remoteConfig().refreshAdsDate() } else { interval = KMKdanRemoteConfig.remoteConfig().refreshAdsDateEvaluateAfter() } timer = Timer.scheduledTimer(timeInterval: TimeInterval(interval), target: self, selector: #selector(adsWebView_Switch), userInfo: nil, repeats: true) RunLoop.current.add(timer!, forMode: .common) closeButton.alphaValue = 1.0 adsWebView.frame = bounds } // MARK: - WKNavigationDelegate func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) { if navigationAction.navigationType == .linkActivated { let tURL = navigationAction.request.url if tURL != nil { buttonItemClicked_Open(tURL as Any) decisionHandler(.cancel) return } } decisionHandler(.allow) } func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) { webView.evaluateJavaScript("document.body.scrollHeight") { [weak self] (result, error) in if let height = result as? CGFloat, height > 0 { let newHeight = min(height, kAD_View_Height) self?.adsWebView.frame = NSRect(x: 0, y: 0, width: (self?.adsWebView.frame.size.width)!, height: newHeight) self?.frame.size.height = newHeight self?.closeButton.frame = NSRect(x: (self?.frame.size.width)! - 30, y: (self?.frame.size.height)! - 30, width: 30, height: 30) } } } func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) { print(error) } @objc func handleUserHaveClickRateUsNotification(_ notification: Notification) { adsWebView_Switch() } }