0

Summary

I am currently faced with an issue that always worked fine in Objective-C, but I can't seem to get it to work in Swift (specifically v3). This is all built using Storyboards. I have two separate view controllers:

  1. ViewController: WKWebView & UIButtonItems
  2. SideMenuTableView: UITableView & UIButtons

Currently, the SideMenu pops over, modally, on top of the ViewController and lists a set of actions via UIButtons. I guess I'll just go off of the most simplistic example, just to get the idea down. What I am trying to accomplish in this example is reloading the WKWebView (ViewController) from a UIButton tap (SideMenuTableView). Below is a more clear picture of what I am trying to get at if things are still unclear:

Screenshots.png

Currently, I can call the SideMenu with a simple Storyboard segue (Kind: Present Modally). Furthermore, I can dismiss the SideMenu which was implemented into a simple close() function. However, when attempting to call the refresh function, I receive the following error message:

fatal error: unexpectedly found nil while unwrapping an Optional value

Code

import UIKit
import WebKit

class ViewController: UIViewController, UINavigationControllerDelegate, WKNavigationDelegate {

    var webView: WKWebView!

    override func viewDidLoad() {
        super.viewDidLoad()

        let webURL = URL(string: "https://google.ca")
        let webRequest = URLRequest(url: webURL!)
        webView.load(webRequest)

    func refresh() {
        webView.reload()
    }
}



import Foundation    

class SideMenuTableView: UITableViewController {

    @IBAction fileprivate func close() {
        self.dismiss(animated: true, completion: nil)
    }

    @IBAction func refresh(sender: AnyObject!) {
        ViewController().refresh()
        close()
    }
}

EDIT: Updated segue code.

// MARK: - Navigation

    // In a storyboard-based application, you will often want to do a little preparation before navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Get the new view controller using segue.destinationViewController.
        // Pass the selected object to the new view controller.
        if let vc = segue.destination as? SideMenuTableView {
            vc.delegate = self
        }
    }
}
JDev
  • 5,168
  • 6
  • 40
  • 61
  • 1
    Possible duplicate of [What does "fatal error: unexpectedly found nil while unwrapping an Optional value" mean?](http://stackoverflow.com/questions/32170456/what-does-fatal-error-unexpectedly-found-nil-while-unwrapping-an-optional-valu) – JAL Jan 16 '17 at 23:11
  • 2
    BTW `ViewController().refresh()` doesn't make any sense. You're initializing a **new** view controller and calling `refresh` on it? – JAL Jan 16 '17 at 23:13
  • Thank you @JAL! I was wondering what I was doing exactly with that code. – JDev Jan 17 '17 at 23:36

1 Answers1

3

You are initializing a new UIViewController with ViewController().refresh(). A delegate pattern is a nicer way to accomplish what you are trying:

Create a protocol:

protocol MainViewControllerDelegate /* Can be called whatever you like */ {
    func refresh()
}

And make your ViewController conform to it.

In your SideMenuTableView create a variable to hold the reference to it's delegates.

weak var delegate: MainViewControllerDelegate? 

In your prepareForSegue() function of ViewController. Check to see if the destination segue is your SideMenuTableView:

if let vc = destination.vc as? SideMenuTableView {
    vc.delegate = self
}

Now in your @IBAction button tap in SideMenuTableView call the delegate method.

@IBAction func refresh(sender: AnyObject!) {
        delegate?.refresh()
        close()
}

Because your ViewController conforms to this protocol (it has to have a refresh function in it's class), the refresh function will be executed.

Emptyless
  • 2,964
  • 3
  • 20
  • 30
  • Ah! This was the solution I was looking for! Now, in a beginners perspective, where do I go about implementing the protocol, as well as conforming it to the ViewController? Do I place it as an extension-type snippet inside the ViewController, or place it in the AppDelegate? – JDev Jan 17 '17 at 23:46
  • I usually place my protocols in the same file below my Class it is a delegate off. You conform to it by writing your protol name behind the class name like you did with the other delegates. Then create a function with the same name and parameters as you described in the protocol but now in your class. – Emptyless Jan 18 '17 at 00:32
  • Thank you very much! I have always wondered about the proper placement of extensions, protocols and such. – JDev Jan 18 '17 at 14:46
  • I also updated (added an edit to) my code to figure out the final issue. Could you explain why the code I have doesn't work and what exactly your code is doing? Still trying to grasp my head around the whole passing of data between ViewControllers thing. – JDev Jan 18 '17 at 15:04
  • And if you have the time, could you explain when I should be using this to pass information, as opposed to something like UserDefaults. This seems a lot more stable as a solution. – JDev Jan 18 '17 at 15:05
  • Every time you want something to respond to an action you use a delegate. Everytime u want to store/cache information (small pieces else use Core Data) you use NSUserDefaults. This is a response to an action so you use a delegate. If you were to save the state of your Menu (for example custom sorting of menu elements) you use NSUserDefaults. – Emptyless Jan 18 '17 at 15:16
  • This is a very good point! Thanks for sharing. :) You have been much help in not only helping me with the code, but explaining how and why it works. I believe that every person teaching should go about this route of showing how and why something works, rather than "here's the code, go copy and paste." – JDev Jan 18 '17 at 17:34
  • Any ideas as to what I am doing wrong in my **EDIT**. The full code can actually be found on my open source GitHub project: https://github.com/revblaze/WiBlaze – JDev Jan 18 '17 at 17:49
  • Managed to fix the error; however, the code is not calling the `refresh()` function in the ViewController. Debugging via NSLogs, I can see that the `refresh(sender: AnyObject!)` function is being called from the SideMenuTableView – but again, it is not firing the function in the ViewController. – JDev Jan 18 '17 at 18:34
  • I've looked at your code but I can't find your custom tableview. Have you committed it yet? – Emptyless Jan 18 '17 at 18:40
  • Some of the customization comes from an external GitHub project. Here are the files from the Pod classes: https://github.com/revblaze/WiBlaze/tree/master/Pods/SideMenu/Pod/Classes – JDev Jan 18 '17 at 19:03
  • 1
    I would recommend watching: https://www.youtube.com/watch?v=D-xXRSCLNFQ, it will explain some of the basic concepts. I think visually with verbal explanation might help ;) If you still have problems after watching this then feel free to ask. – Emptyless Jan 19 '17 at 09:07