iOS 하이브리드앱(기본 브라우져 사용)에서시 서버의 Front에서 javaScript를 이용하여 연동하는 방법
(Swift에서 WKWebView를 이용하는 경우)
1. Native앱 ---> Front의 javaScript로 데이터 전송시 (swift)
1) [[Native앱]] 웹브라우져에서 자바스크립트를 호출한다.
A. HTML로딩시 처음 호출하는 경우 자바스크립트 호출 (설정등 사용시)
// native call -> js (이건 html 로드시에만 가능)
let mySource = "mergeAppUserInfo('\(myUserid)','\(myVersion!)','ios','\(iosVersion)')"
let userScript = WKUserScript(source: mySource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
contentController.addUserScript(userScript)
B. 필요할때마다 자바스크립트 호출, WKWebView의 evaluateJavaScript 함수를 이용한다.
let myJsFuncName = "mergeAppUserInfo('\(myUserid)','\(myVersion!)','ios','\(iosVersion)')"
// native call -> js (이건 언제든지 가능)
mainWebView.evaluateJavaScript(myJsFuncName) { (result, error) in
if let result = result{
print(result)
}
}
2) [[JavaScript]] 서버의 jsp,asp,php등의 파일에서 자바스크립트 설정을 한다.
<script type="text/javascript">
// Native App에서 호출, 접속한 사용자 및 버전 정보 (앱 --> Front)
function mergeAppUserInfo(userid, app_version, os_type, os_version){
alert("AppInfo:"+userid+", "+app_version+","+os_type+", "+os_version);
}
</script>
2. Front의 JavaScript ---> Native앱으로 데이터 전송시 (swift)
1) [[Front의 JavaScript]] 서버의 jsp,asp,php등의 파일에서 자바스크립트로 데이터 전송 요청을 한다.
<script type="text/javascript">
//하이브리드앱 연동, 앱에 데이터를 전송한다.
$(document).ready(function(){
var sendData = "javascript에서 native로 데이터를 전송한다.";
window.webkit.messageHandlers.sendUserIdVersion.postMessage(sendData); // (JavaScript --> 앱)
});
</script>
2) [[Native앱]] 웹브라우져에서 자바스크립트를 호출한다.
// js -> native call 위해 설정, 이용하는 자바스크립트 함수명 등록한다.
...
contentController.add(self, name: "sendUserIdVersion")
configuration.userContentController = contentController
...
//WKScriptMessageHandler에서 제공하는 함수이용하여 데이터 수신
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "sendUserIdVersion"){ // 사용자id, 앱의 최신버전, 업데이트 팝업 보여줄지 여부
if let myBody = message.body as? String {
print("@@@ body=\(myBody)")
}
}
}
[참고]안드로이드 하이브리드앱(기본 브라우져 사용)시 서버의 Front에서 javascript를 이용하여 연동하는 방법
https://androi.tistory.com/352
3. Front의 JavaScript의 전체 소스
<script type="text/javascript">
//하이브리드앱 연동
$(document).ready(function(){
var appInfo = "<c:out value='${appInfo}'/>";
var androidVer = "<c:out value='${appInfo[0].APP_VERSION}'/>";
var iosVer = "<c:out value='${appInfo[1].APP_VERSION}'/>";
var androidMustYn = "<c:out value='${appInfo[0].MUST_YN}'/>";
var iosMustYn = "<c:out value='${appInfo[1].MUST_YN}'/>";
// console.log("appInfo="+appInfo);
console.log("androidVer="+androidVer);
console.log("iosVer="+iosVer);
var agent = navigator.userAgent.toLowerCase();
console.log("agent="+agent);
var isAppIOS = (agent.match('iosapp') != null);
var isAppAndroid = (agent.match('androidapp') != null);
//alert("<%=sMemberId%>");
console.log("sMemberId=<%=sMemberId%>"); // 로그인이 안된 상태이면 "" 값이 넘어감.
if(isAppAndroid) // 안드로이드 APP 인 경우
{
window.HybridApp.sendUserIdVersion("<%=sMemberId%>", androidVer, androidMustYn); // (Front의 JavaScript--> 앱)
}else if(isAppIOS){ // 아이폰 APP 인 경우
var sendData = {
'userid': "<%=sMemberId%>",
'iosVer': iosVer,
'iosMustYn': iosMustYn
};
window.webkit.messageHandlers.sendUserIdVersion.postMessage(sendData); // (Front의 JavaScript --> 앱)
}
});
// Native App에서 호출, 접속한 사용자 및 버전 정보 (앱 --> Front)
function mergeAppUserInfo(userid, app_version, os_type, os_version){
//alert("AppInfo:"+userid+", "+app_version+","+os_type+", "+os_version);
$.ajax({
type: "POST",
cache:false,
url: '<c:url value="/main/ajaxMergeAppUserInfo.json"/>',
dataType: "json",
data: {userid : userid, app_version : app_version ,os_type : os_type, os_version : os_version},
async:false,
success: function(obj) {
console.log("접속한 사용자 정보가 갱신되었습니다.");
},
error: function(obj) {
console.log("접속한 사용자 정보가 갱신실패 되었습니다.");
}
});
}
</script>
iOS 앱을 호출시 전달하는 데이터가 없을때도 임의의 값을 넣어야 동작하네요
window.webkit.messageHandlers.sendUserIdVersion.postMessage(); // 앱에서 호출 안됨 ㅠㅠ
window.webkit.messageHandlers.sendUserIdVersion.postMessage("abc"); // 앱에서 호출됨 ^^
4. Native 앱의 전체 소스
//
// ViewController.swift
import UIKit
import WebKit
import SafariServices
class ViewController: UIViewController, OpenableCustomURL {
let url = URL(string: "http://m.mymobilesite.com/")
let myVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
var myUserid : String = ""
let iosVersion = UIDevice.current.systemVersion
let contentController = WKUserContentController()
var mainWebView = WKWebView()
var cookie: HTTPCookie?
let processPool: WKProcessPool = WKProcessPool()
let statusView: UIView = {
let view = UIView()
view.backgroundColor = .white
view.translatesAutoresizingMaskIntoConstraints = false
return view
}()
let bt_dismiss: UIButton = {
let button = UIButton()
button.setImage(UIImage(named: "ico_gohome.png"), for: UIControl.State.normal)
button.layer.opacity = 1.0
button.addTarget(self, action: #selector(handleDismiss), for: .touchUpInside)
button.layer.cornerRadius = 20
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
@objc func handleDismiss() {
mainWebView.load(url!)
}
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
bt_dismiss.isHidden = true
setupWebView()
setupLayout()
mainWebView.load(url!)
}
func pushLoad(pushurl : String){
guard let linkUrl = URL(string: pushurl) else { return }
print("ViewController linkUrl: \(String(describing: linkUrl))")
mainWebView.load(linkUrl)
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.evaluateJavaScript("navigator.userAgent") { [weak webView] (result, error) in
if let webView = webView, let userAgent = result as? String {
webView.customUserAgent = userAgent + " iosapp" // iOS앱 구분위해 useragent에 추가하고, Front의 javascript에서 이를 사용한다.
}
}
}
func setupWebView() {
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
preferences.javaScriptCanOpenWindowsAutomatically = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.processPool = WKProcessPool()
configuration.allowsInlineMediaPlayback = true
configuration.allowsPictureInPictureMediaPlayback = true
configuration.processPool = processPool
// js -> native call
contentController.add(self, name: "sendUserIdVersion")
configuration.userContentController = contentController
// native call -> js (이건 html 로드시에만 가능)
// let mySource = "mergeAppUserInfo('\(myUserid)','\(myVersion!)','ios','\(iosVersion)')"
// let userScript = WKUserScript(source: mySource, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
// contentController.addUserScript(userScript)
mainWebView.scrollView.bounces = false
mainWebView = WKWebView(frame: .zero, configuration: configuration)
mainWebView.scrollView.isScrollEnabled = true
mainWebView.scrollView.bounces = false
mainWebView.uiDelegate = self
mainWebView.navigationDelegate = self
mainWebView.allowsLinkPreview = true
mainWebView.allowsBackForwardNavigationGestures = true
mainWebView.translatesAutoresizingMaskIntoConstraints = false
}
func setupLayout() {
view.addSubview(statusView)
view.addSubview(mainWebView)
view.addSubview(bt_dismiss)
NSLayoutConstraint.activate([
statusView.topAnchor.constraint(equalTo: view.topAnchor),
statusView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
statusView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
statusView.heightAnchor.constraint(equalToConstant: 20),
mainWebView.topAnchor.constraint(equalTo: statusView.bottomAnchor),
mainWebView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
mainWebView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
mainWebView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
bt_dismiss.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -30),
bt_dismiss.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
bt_dismiss.widthAnchor.constraint(equalToConstant: 40),
bt_dismiss.heightAnchor.constraint(equalToConstant: 40)
])
}
}
extension ViewController: WKUIDelegate, WKNavigationDelegate, WKScriptMessageHandler {
func detectBridges() {
let controller = WKUserContentController()
mainWebView.configuration.userContentController = controller
}
func sendUserData(){
if let myVersion = myVersion{
print("@@@ myVersion: \(myVersion)")
}
let myJsFuncName = "mergeAppUserInfo('\(myUserid)','\(myVersion!)','ios','\(iosVersion)')"
print("@@@ myJsFuncName=\(myJsFuncName)")
// native call -> js (이건 언제든지 가능)
mainWebView.evaluateJavaScript(myJsFuncName) { (result, error) in
if let result = result{
print(result)
}
}
}
// javascript -> native 호출,
func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
if(message.name == "sendUserIdVersion"){ // 사용자id, 앱의 최신버전, 업데이트 팝업 보여줄지 여부
// 인자를 여러개 받을때는 Dictionary로 받는다.
if let dictionary : [String: String] = message.body as? Dictionary{
if let userid = dictionary["userid"]{
print("@@@ userid=\(userid)")
myUserid = userid
}
if let iosVer = dictionary["iosVer"]{
print("@@@ iosVer=\(iosVer)")
}
if let iosMustYn = dictionary["iosMustYn"]{
print("@@@ iosMustYn=\(iosMustYn)")
}
}
// 인자가 1개일때는 String으로 받는다.
// if let myBody = message.body as? String {
// print("@@@ body=\(myBody)")
// }
sendUserData()
}
}
// window.open() 이 호출될때 사용
func webView(_ webView: WKWebView, createWebViewWith configuration: WKWebViewConfiguration, for navigationAction: WKNavigationAction, windowFeatures: WKWindowFeatures) -> WKWebView? {
guard let requestURL = navigationAction.request.url else {return nil}
// let url2 = requestURL.absoluteString
let hostAddress = navigationAction.request.url?.host
if hostAddress == "m.mymobilesite.com" {
bt_dismiss.isHidden = true
mainWebView.load(requestURL)
return nil
}
// let url2 = navigationAction.request.url
let webVC = PresentedWebViewController()
// webVC.url = url2!
webVC.url = requestURL
webVC.processPool = self.processPool
present(webVC, animated: true, completion: nil)
return nil
}
func webView(_ webView: WKWebView, runJavaScriptConfirmPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping (Bool) -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let yes = UIAlertAction(title: "네", style: .default) {
action in completionHandler(true)
}
let no = UIAlertAction(title: "취소", style: .default) {
action in completionHandler(true)
}
alertController.addAction(no)
alertController.addAction(yes)
present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
let alertController = UIAlertController(title: nil, message: message, preferredStyle: .alert)
let otherAction = UIAlertAction(title: "확인", style: .default) {
action in completionHandler()
self.navigationController?.popViewController(animated: true)
}
alertController.addAction(otherAction)
present(alertController, animated: true, completion: nil)
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Swift.Void) {
guard let requestURL = navigationAction.request.url else {return}
let url = requestURL.absoluteString
let hostAddress = navigationAction.request.url?.host
// To connnect app store
if hostAddress == "itunes.apple.com" {
if UIApplication.shared.canOpenURL(requestURL) {
UIApplication.shared.open(requestURL, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
decisionHandler(.cancel)
return
}
}
if url.hasPrefix("mywindowpop://http//") { // 이 표시가 있으면 기본브라우져 새창을 띄움
let new_url = url.replacingOccurrences(of: "mywindowpop://http//", with: "http://")
if let myUrl = URL(string: new_url) {
if UIApplication.shared.canOpenURL(myUrl) {
UIApplication.shared.open(myUrl, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
decisionHandler(.cancel)
return
}
}
}
if url.hasPrefix("mywindowpop://https//") { // 이 표시가 있으면 기본브라우져 새창을 띄움
let new_url = url.replacingOccurrences(of: "mywindowpop://https//", with: "https://")
if let myUrl = URL(string: new_url) {
if UIApplication.shared.canOpenURL(myUrl) {
UIApplication.shared.open(myUrl, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
decisionHandler(.cancel)
return
}
}
}
if hostAddress == "m.mymobilesite.com" || hostAddress == "" {
bt_dismiss.isHidden = true
}else{
bt_dismiss.isHidden = false
}
#if DEBUG
print("url = \(url), host = \(hostAddress?.description ?? "")")
#endif
let url_elements = url.components(separatedBy: ":")
if url_elements[0].contains("http") == false &&
url_elements[0].contains("https") == false { // 신용카드 결제 화면등을 위해 필요
if UIApplication.shared.canOpenURL(requestURL) {
UIApplication.shared.open(requestURL, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
print("@@@ Browser can't be opened, but Scheme try to call !! @@@")
UIApplication.shared.open(requestURL, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
}
decisionHandler(.cancel)
return
}
decisionHandler(.allow)
}
}
protocol OpenableCustomURL {
func openCustomApp(urlScheme:String, additional_info:String)
}
extension OpenableCustomURL {
func openCustomApp(urlScheme:String, additional_info:String) {
if let requestUrl = URL(string:"\(urlScheme)"+"\(additional_info)") {
let application:UIApplication = UIApplication.shared
if application.canOpenURL(requestUrl) {
application.open(requestUrl, options: convertToUIApplicationOpenExternalURLOptionsKeyDictionary([:]), completionHandler: nil)
} else {
print("cant Open2")
}
}
}
}
// Helper function inserted by Swift 4.2 migrator.
fileprivate func convertToUIApplicationOpenExternalURLOptionsKeyDictionary(_ input: [String: Any]) -> [UIApplication.OpenExternalURLOptionsKey: Any] {
return Dictionary(uniqueKeysWithValues: input.map { key, value in (UIApplication.OpenExternalURLOptionsKey(rawValue: key), value)})
}
참고 URL
https://oingbong.tistory.com/225
'iOS' 카테고리의 다른 글
iOS Swift WKWebView 하이브리드앱에서 신용카드 결제 (0) | 2019.06.03 |
---|---|
iOS Swift WKWebView 쿠키 유지 (0) | 2019.06.03 |
iOS 앱 아이콘 만들기 (0) | 2019.05.07 |
ios swift 키패드, 키보드 닫기, 숨기기 (0) | 2019.03.26 |
아이폰 오버스크롤 및 웹뷰 바운스 막기 (0) | 2015.05.29 |