At first, we will create a simple Web browser with a help of convenient functions of Xcode.
Launch Xcode, and choose "Create a new Xcode project" from the welcome screen. Choose Single View Application from the list and click Next. For Product Name enter SyncBrowser, then make sure you have Swift selected for language.
One of the fields you'll be asked for is "Organization Identifier", which is a unique identifier. For example, edu.isse.pbl1 can be used.
After you created a new project, you will see the following screen by selecting Main.storyboard on the navigation tree on the left side.
On this screen, you can put on the canvas various types of user interface components to interact with users. In this project, we will use a component which works as a Web browser, so look for "WebKit View" in the list which can be shown by clicking the button with a red circle (see the screenshot below), and drag it into the canvas. After that, enlarge the Web Kit View component to the maximum size so that it fits to the entire screen.
Now we will connect this Web Kit View component to the Swift code. Choose View > Assistant Editor > Show Assistant Editor from the menu. Then you will see the following screen. You should now see the detail view controller in Interface Builder in one pane, and in the other pane the source code for ViewController.swift.
To connect the Web Kit View component to the Swift code, follow the steps below.
class ViewController: UIViewController {
and override func viewDidLoad() {
.If you follow those steps, a balloon should appear with five fields: Connection, Object, Name, Type, and Storage.
Leave all of them as they are except for Name – "webView" should be entered as a name of a variable. Then click the connect button and Xcode will enter a line of code into ViewController.swift like this.
class ViewController: UIViewController {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
Now you can hide the Assistant Editor by choosing Choose View > Standard Editor > Show Standard Editor.
At this point, you will find that Xcode is reporting an error. This is because WKWebView cannot be found in a standard library, so you have to insert a line of code in the importing section on the top of the code like this.
import UIKit
import WebKit
The next step is to add some codes to ViewController.swift to make the Web browser work. First, add WKNavigationDelegate in the declaration of the class like this.
class ViewController: UIViewController, WKNavigationDelegate {
Second, add the following two lines into the function called viewDidLoad to set some important attributes.
webView.navigationDelegate = self
webView.allowsBackForwardNavigationGestures = true
Third, add the following function after the viewDidLoad function. This new function is to load a web page into the Web Kit View, specified by an argument url.
func loadPage(url: String) {
if(webView.url?.absoluteString == url) { // no need to open the same URL
return
}
let myURL = URL(string: url)
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
Finally, add a line of code at the end of the viewDidLoad function to load an initial Web page (Microsoft's Bing site).
loadPage(url: "https://www.bing.com/")
Then the entire code of ViewController.swift looks like this.
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
@IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
webView.navigationDelegate = self
webView.allowsBackForwardNavigationGestures = true
loadPage(url: "https://www.bing.com/")
}
func loadPage(url: String) {
if(webView.url?.absoluteString == url) { // no need to open the same URL
return
}
let myURL = URL(string: url)
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
}
}
Before running the program, you have to add a library for the WebKit View framework. Select the "SyncBrowser" node on the project tree, look for the "Linked Frameworks and Libraries" section on the center pane, click "+" on the section, then choose and add the "WebKit.framework" on the center window.
To run the program, just press the arrow button in the top-left part of the window. You can choose any type of iOS terminals. When the program is started, the Bing page should be shown. You can enter any query words and follow a link as you use it on your smartphone. Holizontally swiping the screen fron the edge lets you navigate the browsing history back and forth (because you have set the allowsBackForwardNavigationGestures attribute to true in the code).
Now you have obtained a simple Web browser, so we will add a bunch of codes to connect the terminals each other and synchronize the Web page shown on the screen.
First, add a new line of code to import a library called MultipeerConnectivity. This library enables iOS terminals to connect each other in a peer-to-peer manner, which means you don't need a server to intermediate the communication among terminals.
import MultipeerConnectivity
Second, add the following variable declarations to store the information about MultipeerConnectivity after the declaration of the webView variable.
var peerID: MCPeerID!
var mcSession: MCSession!
var mcAdvertiserAssistant: MCAdvertiserAssistant!
Third, add MCSessionDelegate and MCBrowserViewControllerDelegate classes to the declaration of the ViewController class.
class ViewController: UIViewController, WKNavigationDelegate, MCSessionDelegate, MCBrowserViewControllerDelegate {
Fourth, add the following lines of code to initialize the MultipeerConnectivity to the part before loadPage() in the viewDidLoad function.
peerID = MCPeerID(displayName: UIDevice.current.name)
mcSession = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
mcSession.delegate = self
Now you will see that Xcode is reporting errors again. This is because the code is missing important functions to make MultipeerConnectivity work. To avoid the errors produced, add the following empty functions after the loadPage() function. These functions handle various types of status changes (connected, disconnected, data received, etc.) of a MultipeerConnectivity channel.
func session(_ session: MCSession, didReceive stream: InputStream, withName streamName: String, fromPeer peerID: MCPeerID) {
}
func session(_ session: MCSession, didStartReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, with progress: Progress) {
}
func session(_ session: MCSession, didFinishReceivingResourceWithName resourceName: String, fromPeer peerID: MCPeerID, at localURL: URL?, withError error: Error?) {
}
func session(_ session: MCSession, peer peerID: MCPeerID, didChange state: MCSessionState) {
}
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
}
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
}
func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
}
Then we will add some functions to actually connect terminals each other. In MultipeerConnectivity, one of the terminals in a group advertises its existence as a leader and other terminals find the leader and connect it. The folllowing two functions realize this behavior.
func startHosting() {
mcAdvertiserAssistant = MCAdvertiserAssistant(serviceType: "pbl1-isse-01", discoveryInfo: nil, session: mcSession)
mcAdvertiserAssistant.start()
}
func joinSession() {
let mcBrowser = MCBrowserViewController(serviceType: "pbl1-isse-01", session: mcSession)
mcBrowser.delegate = self
present(mcBrowser, animated: true)
}
The startHosting() function will start advertising as a leader. On the other hand, the joinSession() function will join a group which has been already advertised by one of the terminals. Note that the service type "pbl1-isse-01" should be changed to some other name of your group's favorite. Otherwise, you will be connected with another group which is connected under the same Wi-Fi network.
Then we will add some lines of codes to call these functions when the application is started up. We want the app to try to connect the existing group first, and if it doesn't exist then make a new group. To make the app do this behavior, add the following function after the viewDidLoad function.
override func viewDidAppear(_ animated: Bool) {
if mcAdvertiserAssistant == nil && mcSession.connectedPeers.count == 0 {
joinSession()
}
}
This function lets the app call the joinSession() function when the WebKit View is shown on the screen. The joinSession() function will show a dialog to choose a group in a list. If there is no group which has been created or the user doesn't want to join any group in a list, we expect the user to press the Cancel button. In this case, the app should create a new group the other users can join. This behavior can be coded in the browserViewControllerWasCancelled() function. We have already added an empty one, so add the content of this function as follows.
func browserViewControllerWasCancelled(_ browserViewController: MCBrowserViewController) {
dismiss(animated: true)
startHosting()
}
As written in this code, the group selection dialog will be closed and the startHosting() function will be called to become a leader when the Cancel button is pressed. On the other hand, when the Done button is pressed in the group selection dialog, the app should simply close the dialog. This behavior can be coded as follows.
func browserViewControllerDidFinish(_ browserViewController: MCBrowserViewController) {
dismiss(animated: true)
}
Then we will add a bunch of codes to synchronize the Web page opened in the browser. The synchronization behavior is described as follows: when a new page is opened in one of the terminals, a URL of the page is sent to all other terminals, and when a terminal receives the URL sent, it opens the page on the Web browser. To send a URL when a new page is opened, add the following function.
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
if mcSession.connectedPeers.count > 0 {
if let urlString = webView.url?.absoluteString {
let data = urlString.data(using: .utf8)
do {
try mcSession.send(data!, toPeers: mcSession.connectedPeers, with: .reliable)
} catch {
let ac = UIAlertController(title: "Send error", message: error.localizedDescription, preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
present(ac, animated: true)
}
}
}
}
This webView() function is called when the page navigation happens. This function obtains the URL of the Web page to be opened, and send it to connected peers through the MultipeerConnectivity channel.
The final thing to do is to add some lines of code for the behavior when the URL is received from other terminals. This can be done by filling the content in one of the empty functions which have been already added. Change the last of the session functions as follows.
func session(_ session: MCSession, didReceive data: Data, fromPeer peerID: MCPeerID) {
let str: String? = String(data: data, encoding: .utf8)
DispatchQueue.main.async { [unowned self] in
self.loadPage(url: str!)
}
}
That's all for coding! Congratulations.
Copyright © 2018-2019 Hideyuki Takada