Test HTTP Requests Tools Blog Learn Quizzes Smile API Log In / Sign Up
Test HTTP Requests Tools Blog Learn Quizzes Smile API Log In / Sign Up
« Return to the tutorials list
We have updated the website and our policies to make sure your privacy rights and security are respected.
Click here to learn more about the way our website handles your data.

Remove this message.

You can read this article in: English :: Español :: русский

Правильное обращение к JavaScript в WKWebView и SWift 3.0

Daniel Gheorghe Difficulty: 20 / 50 Tweet
swift-js-wkwebview-poker-game

Здравствуйте!

В этом уроке я хочу поделиться с вами своим опытом работы с WebViews в Swift 3.0 и объяснить, как я обеспечил обмен данными между JavaScriptи родным кодом.

Для моего проекта мне нужно было хранить данные в постоянном хранилище UserDefaults и сделать их доступными для офлайнового использования кодом на JavaScript.

В частности, мне нужно было поприветствовать пользователя вступительным видео при первой загрузке приложения и отметить это событие изменением переменной длоя отображения основного содержимого при последующих запусках.

Позднее возникла потребность в отображении данных пользователя через webView

Шаг 1: Начнем с файла AppDelegate.swift

Первым моим действием было копирование всего содержимого папки WebView в каталоге .documentDirectory моего приложения - таким образом я получил доступ к файлам. В приложении я проверял функцией AppDelegate наличие каталога в .documentDirectory и, если его там не было, создавал его.

Мое приложение - видеопокер, в исходном коде вы увидите много отсылок к этому.

    
        let sharedpath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
        let gamefolder = sharedpath.path + "/videopoker";
        if( FileManager.default.fileExists( atPath: gamefolder ) == false ){
            do {
                try FileManager.default.copyItem(
                    atPath: Bundle.main.url(forResource: "videopoker", withExtension:"")!.path,
                    toPath: gamefolder
                )
            } catch {
                print("Couldn't copy videopoker folder");
                print(error);
            }
        }
    

Шаг 2: Поиграем с файлом вступительного видео

Выставив настройки в файле ViewController, импортируйте библиотеки, необходимые для воспроизведения видео методом viewDidAppear.

Также мы воспользуемся NotificationCenter, чтобы добавить функцию отслеживания, которая сообщит об окончании воспроизведения видео.

Для этого у нас будет выделена постоянная переменная - "appInitializedOnce", впоследствии она будет использоваться для подтверждения однократного воспроизведения видео или, что удобнее, что приложение было запущено в первый раз.

    
        import AVKit;
        import AVFoundation;

        // ...

        self.moviePlayerViewController = AVPlayerViewController()
        let path = Bundle.main.path(forResource: "1", ofType: "mp4"); // файл видео (не забудьте импортировать его в xcode)
        let url = NSURL.fileURL(withPath: path!)
        self.moviePlayer = AVPlayer(url: url);

        self.moviePlayerViewController!.showsPlaybackControls = false;
        self.moviePlayerViewController!.player = moviePlayer;
        self.present(self.moviePlayerViewController!, animated: true) {
            self.moviePlayerViewController!.player!.play()
        }
        
        // добавление наблюдателя, который вызовет функцию "playerDidFinishPlaying"
        NotificationCenter.default.addObserver(self, selector: #selector(ViewController.playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: self.moviePlayer?.currentItem)

        // ...
        func playerDidFinishPlaying() {
            NotificationCenter.default.removeObserver(self); // теперь наблюдателя можно удалить
            let userDefaults = UserDefaults.standard
            userDefaults.set(true, forKey: "appInitializedOnce"); // изменить значение "appInitializedOnce"
            
            //возврат в основной поток
            OperationQueue.main.addOperation {
                let gameViewController = self.storyboard?.instantiateViewController(withIdentifier: "Game") as! GameViewController
                self.moviePlayerViewController?.present(gameViewController, animated: true, completion: nil)
            }
        }
    

Шаг 3: Теперь мы отобразим GameViewController, который, в свою очередь, загрузит html страницу с нашей игрой

Маленькая игра написана мной на JavaScript, так что мне нужен WebView для загрузки html из папки, скопированной на Шаге 1.

Содержимое WebView не имеет прямого оношения к теме этого урока, так что мы сосредоточимся на обмене данными между основным кодом и JavaScript.

GameViewController требует активации нескольких настроеки для обмена данными между кодом swift и JavaScript.

WKUserContentController и WKScriptMessageHandler позволят ввести код на JavaScript code в DOM и отслеживать "отправленные" из WKWebView сообщения.

В общем виде приведенный ниже код делает следующее:

  1. создает WKUserContentController
  2. создает WKUserScript
  3. помещает WKUserScript в WKUserContentController
  4. создает обработчик объектов JS с обратной связью
  5. помещает все вышеприведенное в объект WKWebViewConfiguration
  6. загружает WKWebView с заданной конфигурацией
  7. загржает файл по ссылке URL, открывая доступ к ".documentDirectory" (иначе такие фунцкции HTML 5 как тэги видео работать не будут)
  8. показать WKWebView
    
        import UIKit
        import WebKit;
        import JavaScriptCore;

        class GameViewController: UIViewController, WKScriptMessageHandler {
            var webview: WKWebView?;
            // ...
            override func viewDidAppear(_ animated: Bool) {
                let contentController = WKUserContentController();
                let userScript: WKUserScript;
                // the injected javascript
                userScript = WKUserScript(
                    source: "console.log('test')",
                    injectionTime: WKUserScriptInjectionTime.atDocumentEnd,
                    forMainFrameOnly: true
                )

                contentController.addUserScript(userScript);
                //set the native handlers for the post back of the JS methods
                contentController.add(
                    self,
                    name: "nativeCallbackHandler"
                );

                // помещение созданной конфигурации в новый wkwebview
                let config = WKWebViewConfiguration()
                config.userContentController = contentController
                self.webview = WKWebView(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: self.view.frame.height), configuration: config);
                
                // помещение wkwebview в окно и загрузка html по ссылке после закрытия ранее открытых webview
                for subview in self.view.subviews {
                    if(subview is WKWebView) {
                        print("Found the previous wkwebview");
                        subview.removeFromSuperview();
                    }
                }
                self.view.addSubview(self.webview!)
                
                // загрузка html из папки, скопированной на шаге 1 
                // открытие доступа к корню каталога ".documentDirectory"
                let folderPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!;
                let url = URL(fileURLWithPath: folderPath.path + "/videopoker/index.html");
                self.webview!.loadFileURL(url, allowingReadAccessTo: folderPath);
            }
        }
    

Шаг 4: Обработка "почты" от JS

Создайте в том же GameViewController функцию userContentController для обработки любых данных, приходящих из WKWebView.

Из JavaScript данные выводятся следующим образом:

window.webkit.messageHandlers.nativeCallbackHandler.postMessage(JSON.stringify({"token": token, "user_id": "" + user_id}));
    	
        func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
            if (message.name == "nativeCallbackHandler") {
                // независимо от того, что вы хотите сделать
                // ниже приведен пример использования описанного выше вызова JavaScript
                let userDefaults = UserDefaults.standard
                userDefaults.set(message.body, forKey: "userData")
            }
        }
	

Шаг 5: Не показывайте видео, если оно уже было показано

Как вы помните из шага 2, мы сохранили переменную "appInitializedOnce" как true после завершения воспроизведения видео. Теперь нам надо иметь это ввиду при загрузке приложения, так что мы вернемся к AppDelegate.swift и добавим приведенный ниже код - сразу перейдем к GameViewController если ранее приложение запускалось хотя бы один раз.

    
        //...
        //если userData существует (пользователь уже видел видео), то вступительное видео больше показываться не будет
        let userDefaults = UserDefaults.standard
        let appInitializedOnce = userDefaults.bool(forKey: "appInitializedOnce");
        if ( appInitializedOnce == true ) {
            self.window = UIWindow(frame: UIScreen.main.bounds)
            let storyboard = UIStoryboard(name: "Main", bundle: nil)
            let initialViewController = storyboard.instantiateViewController(withIdentifier: "Game")
            self.window?.rootViewController = initialViewController
            self.window?.makeKeyAndVisible()
        }
        //...
	

Этот маленький пример предназначен для отработки программистом навыков работы с WebViews в Swift 3.0 и знакомства начинающих разработчиков на Swift с такими концептами как ViewControllers, проигрыватели видео и разработка гибридных приложений для iOS.

Вы можете просмотреть и скопировать весь исходный код приложения на GitHub. Спасибо за внимание, надеюсь этот урок окажется для вас полезным.

comments powered by Disqus