In this lab, we will use WebKit to help us build our own browser for the iPhone. It’s a scaled down browser, but it should have a navigation bar so we can enter in URLs, a reload and stop Botton, and a set of back and forward buttons for simple navigation. Here is what the app will look like when finished:
- 
The 2 elements of the URL Navigation bar are Textelement for the URL label, and aTextFieldelement for typing in the URL. Create a new SwiftUI view calledSearchBar.swift. Create a struct like so:
 struct SearchBar: View {
  var body: some View { }
}
- 
Inside the body,  add the two necessary elements for the search bar like so:
     HStack {
      Text("URL:")
      TextField("URL", text: $viewModel.urlString)
    }
Notice the $viewModel.urlString. This is the binding for URL the user will type in.  We will need to create a view model to keep this value consistent throughout the app.
 
- 
Create a file called ViewModel.swiftand create a class calledViewModeland make it anObservableObjectlike so:
 class ViewModel: ObservableObject { }
- 
Add an published variable for the urlString so the class now looks like this: class ViewModel: ObservableObject {
  @Published var urlString: String = ""
}
- 
Then, add a reference to a ViewModelto theSearchBarstruct.
 @ObservedObject var viewModel: ViewModel
 
- 
Back to SearchBar.swift. To make theTextFieldmore user friendly for typing in URLs, here are some options you can chain to the end of it:
 TextField("URL", text: $viewModel.urlString)
  .keyboardType(.URL)
  .autocapitalization(.none)
  .disableAutocorrection(true)
- 
Now to add the SearchBarto yourContentView. First, store an instance of theViewModelas a constant in yourContentViewstruct. Then, add theSearchBarinside aVStackto thebodyvariable, giving it that instance of theViewModel:
   var body: some View {
    VStack {
      SearchBar(viewModel: viewModel)
    }
- 
Now when you run your app, you should be able to see the URL navigation bar. 
Now let’s create the bottom navigation option button bar.
- 
Let’s create empty methods for our button actions in our ViewModel. Create 5 empty methods for our five empty actions (you can add print statements to these methods to ensure they are working/linked correctly). These methods are:
 
- goBack()
- goForward()
- share()
- refresh()
- stop()
 
- 
Now, let’s link them to our buttons. First, in your BottomBarstruct, create an@ObservedObjectfor yourViewModel.
 Now, in your ContentViewwhere you create yourBottomBaryou will need to pass the instance ofViewModel.
 
- 
Then, add the view model methods to your button actions. You can either add them in the action closure of the buttons like this: Button(action: {
  self.viewModel.goBack()
}) {
  Image(systemName: "chevron.left")
}
Or pass in the function as a parameter like this: Button(action: viewModel.goBack) {
  Image(systemName: "chevron.left")
}
I personally prefer the latter, but it’s up to you! 
- 
Now these buttons still don’t do anything, but make sure they’re linked to your ViewModel by using breakpoints or print statements. 
- 
Create a new SwiftUI view and within it, create a  struct called WebView.
 
- 
Make sure you import WebKit,import Combine, andimport SwiftUI.
 
- 
Make this conform to UIViewRepresentableinstead of having it be an instance ofView(like the top and bottom bars were) and deletebody.
 
- 
Create 2 methods in this WebViewstruct:
 
- func makeUIView(context: Context) -> WKWebView
- func updateUIView(_ webView: WKWebView, context: Context)
 Fill them out like so: func makeUIView(context: Context) -> WKWebView {
  return WKWebView()
}
func updateUIView(_ webView: WKWebView, context: Context) {
  if let url = URL(string: "https://\(viewModel.urlString)") {
    webView.load(URLRequest(url: url))
  }
}
- 
You will need an instance of ViewModelfor the above code. Add a constant for this just as you did forBottomBarandSearchBar
 
- 
Now, in your ContentViewadd an instance ofWebViewbetween yourSearchBarand yourBottomBar.
 
- 
Try this out! You should be able to open web pages using your app, but navigation will not work yet. 
- 
We will come back to these methods later when we implement our navigations actions, but before that, within the WebViewstruct, let’s create a class calledCoordinator, which will be anNSObjectand conform toWKNavigationDelegate.
 
- 
Give this class 2 variables: var parent: WebView
var webViewOptionsSubscriber: AnyCancellable?
 
- 
Create an initfunction for this class, make the init take aWebViewobject and set theparentvariable equal to thisWebView.
 
- 
Also create a deinitlike this:
 deinit {
  webViewOptionsSubscriber?.cancel()
}
- 
Finally, let’s create the most important method of this class: func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) { }
This method is what is going to handle our navigation buttons and utilize Combine to do so. 
- 
Let’s go back to our pretty empty ViewModeland create an enum (outside of the class) for our web view button action options. Enums are great and used heavily in iOS development. I would suggest getting very comfortable with enums. Read more about them here.
 enum WebViewOptions {
  case back
  case forward
  case share
  case refresh
  case stop
}
- 
Now within the ViewModel, let’s create a published variable for which option the user has just tapped:
 @Published var webViewOptionsPublisher = PassthroughSubject<WebViewOptions, Never>()
 You will need to import Combineat the top of yourViewModelin order to use thePassthroughSubject.
 Learn more about a PassthroughSubjecthere.
 
- 
This will allow us to respond to the user’s selections in our WebView. But to do that, we need to send these values in our View Model’s methods. For example, ourgoBack()method will look like this:
 func goBack() {
  webViewOptionsPublisher.send(.back)
}
Fill out the rest of the methods accordingly. This will send the value from the publisher to the subscriber, which we will set up in the WebViewso theWebViewcan respond to the user’s selection. Let’s do that next.
 
- 
Start filling out the Coordinator.webView(_:didStartProvisionalNavigation:)method inWebView.swift.
 This is what it should look like before you fill it out:
     func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
      webViewOptionsSubscriber = parent.viewModel.webViewOptionsPublisher.sink(receiveValue: { webViewOption in
        // create a switch statement for each of the different options
      })
   }
Make sure you understand what .sink()is doing. It is receiving the value we sent using thewebViewOptionsPublisherin theViewModeland we can respond to it in ourWebView.
 Let’s create a switch statement to respond to the different navigation options. This goes inside the .sinkclosure.
 switch webViewOption {
  case .back: return
  case .forward: return
  case .share: return
  case .refresh: return
  case .stop: return
}
- 
Right now we’re just returning, but let’s fill each option out: switch webViewOption {
case .back:
  if webView.canGoBack {
    webView.goBack()
  }
case .forward:
  if webView.canGoForward {
    webView.goForward()
  }
case .share: return // we'll fill this out later
case .refresh:
  webView.reload()
case .stop:
  webView.stopLoading()
}
 
- 
Now let’s make sure our WebViewrespond to these actions. In yourWebViewstruct, add this method:
 func makeCoordinator() -> Coordinator {
  Coordinator(webView: self)
}
- 
And update the WebView.makeUIView(context:)function to look like this:
 func makeUIView(context: Context) -> WKWebView {
  let webView = WKWebView()
  webView.navigationDelegate = context.coordinator
  return webView
}
This method allows our WebViewto use the customCoordinatorwe defined.
 
- 
Now, all buttons on your app should be working except for the share button - try them out! 
- 
Create a Swift file called ShareSheet.swift:
 import SwiftUI
import UIKit
struct ShareSheet: UIViewControllerRepresentable {
  typealias Callback = (_ activityType: UIActivity.ActivityType?, _ completed: Bool, _ returnedItems: [Any]?, _ error: Error?) -> Void
  let activityItems: [Any]
  let applicationActivities: [UIActivity]? = nil
  let excludedActivityTypes: [UIActivity.ActivityType]? = nil
  let callback: Callback? = nil
  func makeUIViewController(context: Context) -> UIActivityViewController {
    let controller = UIActivityViewController(activityItems: activityItems, applicationActivities: applicationActivities)
    controller.excludedActivityTypes = excludedActivityTypes
    controller.completionWithItemsHandler = callback
    return controller
  }
  func updateUIViewController(_ uiViewController: UIActivityViewController, context: Context) { }
}
- 
Add a new variable to the ViewModel:@Published var shouldShowShareSheet: Bool = false
 We will set this to true in WebView.Coordinator.webView(_:didStartProvisionalNavigation:)like so:
 case .share:
  self.parent.viewModel.shouldShowShareSheet = true
 
- 
To present the share sheet, add the following code to the end of the VStackin theContentView.
 Note: due to improvements in how SwiftUI handles if statements the code can be written more concisely in XCode 12 than it can in XCode 11. Please use the appropriate version. XCode 12 and above .sheet(isPresented: $viewModel.shouldShowShareSheet) {
  if let url: URL = URL(string: "https://\(viewModel.urlString)") {
    ShareSheet(activityItems: [url])
  }
}
The ContentView.swiftshould look like this now:
 import SwiftUI
struct ContentView: View {
  @ObservedObject var viewModel = ViewModel()
  var body: some View {
    VStack {
      SearchBar(viewModel: viewModel)
      WebView(viewModel: viewModel)
      BottomBar(viewModel: viewModel)
    }
    .sheet(isPresented: $viewModel.shouldShowShareSheet) {
      if let url: URL = URL(string: "https://\(viewModel.urlString)") {
        ShareSheet(activityItems: [url])
      }
    }
  }
}
XCode 11 and below .sheet(isPresented: $viewModel.shouldShowShareSheet) {
  if URL(string: "https://\(self.viewModel.urlString)") != nil {
    ShareSheet(activityItems: [URL(string: "https://\(self.viewModel.urlString)")!])
  }
}
The ContentView.swiftshould look like this now:
 struct ContentView: View {
  @ObservedObject var viewModel: ViewModel = ViewModel()
  var body: some View {
    VStack {
      SearchBar(viewModel: viewModel)
      WebView(viewModel: viewModel)
      BottomBar(viewModel: viewModel)
    }
    .sheet(isPresented: $viewModel.shouldShowShareSheet) {
      if URL(string: "https://\(self.viewModel.urlString)") != nil {
        ShareSheet(activityItems: [URL(string: "https://\(self.viewModel.urlString)")!])
      }
    }
  }
}
- 
And now all your actions should work! And you have your own simple browser :)