Pushing ViewController Results In Cant Unwrap Optional.None - ios

I have an issue in Swift. When I press a cell on my table I do a segue into another ViewController which has a UIWebView and loads a webpage. This works fine. The custom ViewController code looks like this:
class ViewController_ShowView : UIViewController
{
var showName: String?
var showUrl: String?
#IBOutlet var webView: UIWebView
override func viewDidLoad()
{
super.viewDidLoad()
self.title = showName
var requestURL = NSURL(string: showUrl)
var request = NSURLRequest(URL: requestURL)
webView.loadRequest(request)
}
}
However, I am trying to load the ViewController from the AppDelegate (when a user clicks on a UILocalNotification) and it crashes on the web view.loadRequest(request) line from above. Here is how I am attempting to call it:
var currentShowsViewController = viewController.topViewController as ViewController_CurrentShows
var viewControllerToPush = ViewController_ShowView()
viewControllerToPush.showName = "Test"
viewControllerToPush.showUrl = "http://www.google.com"
currentShowsViewController.navigationController.pushViewController(viewControllerToPush, animated: false)
This causes a crash with the error Cant Unwrap Optional.None
I am confused why it works from the perform segue method but not this way...

Problem caused by this line of code:
var viewControllerToPush = ViewController_ShowView()
You should instantiate view controller from its storyboard. Here is how you can do it:
let storyboard : UIStoryboard = UIStoryboard(name: "MainStoryboard", bundle: nil);
let viewControllerToPush : ViewController_ShowView
= storyboard.instantiateViewControllerWithIdentifier("showViewVC") as ViewController_ShowView;
Note that you should also open your storyboard and set the view controller's identifier to showViewVC.
Remarks:
The exception get thrown because #IBOutlet var webView: UIWebView does not get initialized. #IBOutlets in Swift are optionals thus you receive Cant Unwrap Optional.None.

Related

Direct initializing of a viewController

My Desire Output:
i want to pass data with direct initializing of a second viewController
in my first vc :
class MainViewController : UIViewController {
#IBAction func nextQuestion(_ sender: UIButton ) {
//enter code here
questionIndex += 1
let currentQuestion = exam1[questionIndex]
questionLabel.text = currentQuestion.question
//enter code here
let svc = SecondViewController()
svc.label.text = "some data from mainvc"
}
in my secondvc :
class SecondViewController : UIViewController {
#IBOutlet weak var label : UILabel!
}
// any error is not showed up when I write code an the simulator works. but when it comes to sending data to svc app crashes and gives error " found nil while unwrapping optional"
The problem is that when you do:
let svc = SecondViewController()
You are initializing without the information you put in the Storyboard (assuming you are using storyboards). Hence, so the UILabel is never initialized.
Solution
In order to solve that replace the line:
let svc = SecondViewController()
for
let svc = UIStoryboard(name: "YourStoryboardName", bundle: nil).instantiateViewController(withIdentifier: "YourViewIdentifier") as! SecondViewController

View through IBOutlet or manually created is nil after views hierarchy is loaded

I am creating a macOS app on Xcode 9.2 and i can't figure out why after the views hierarchy is created in viewDidLoad(), i can't use anymore the IBOutlet which is referencing the view in the StoryBoard.
I have found similar question, where they suggest to save the view that i want to update, as a variable, when i am in viewDidLoad() so i can use it later; if so what is the advantage of using an IBOutlet?
Can someone pls explain how to update in this case the content of the PDFView outside viewDidLoad() without getting nil?
The AppDelegate
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
// MARK: - Properties
var filePath: URL?
var result: Int?
var fileObject: Data?
// MARK: - Outlets
#IBOutlet weak var openFileMenuItem: NSMenuItem!
// MARK: - App Life Cycle
func applicationDidFinishLaunching(_ aNotification: Notification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(_ aNotification: Notification) {
// Insert code here to tear down your application
}
// MARK: - My Functions
#IBAction func openFile(_ sender: NSMenuItem) {
//Get the window of the app
var window = NSApplication.shared.mainWindow!
// Create NSOpenPanel and setting properties
let panel = NSOpenPanel()
panel.title = "Select file"
panel.allowsMultipleSelection = false;
panel.canChooseDirectories = false;
panel.canCreateDirectories = false;
panel.canChooseFiles = true;
// Filtering file extension
let fileTypes: [String] = ["pdf"]
panel.allowedFileTypes = fileTypes
// Showing OpenPanel to the user for selecting the file
panel.beginSheetModal(for: window) {
(result) in
if result.rawValue == NSFileHandlingPanelOKButton {
// Getting url of the file
self.filePath = panel.urls[0]
print(self.filePath)
// Creating Data Object of the file for creating later the PDFDocument in the controller
do {
if let fileData = self.filePath {
self.fileObject = try Data.init(contentsOf: self.filePath!)
print("\(self.fileObject) ")
}
} catch let error as NSError {
print("Error with file Data Object: \(error)")
}
// Getting the mainViewController
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let mainController = storyboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier(rawValue: "mainViewControllerID")) as! MainViewController
// Appdelegate call function of mainViewController to update the content of PDFView
mainController.showPdfDocument(fileData: self.fileObject!)
}
}
}
}
The MainViewController:
import Foundation
import Quartz
class MainViewController: NSViewController {
// MARK: - Property
var pdfdocument: PDFDocument?
// MARK: - Outlets
#IBOutlet var mainView: NSView!
#IBOutlet var pdfView: PDFView!
// MARK: - App Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
// MARK: - My Functions
func showPdfDocument(fileData: Data) {
print("appdelegate calling showPdfDocument on mainViewController " )
pdfdocument = PDFDocument(data: fileData)
pdfView.document = pdfdocument
}
}
The Error I Am Getting:
fatal error: unexpectedly found nil while unwrapping an Optional value
I solved the problem with the current code in the AppDelegate. Basically i am using the contentViewController to update the PDFView but i don't know if this is a good practice because i should update the PDFView with its own controller and not with the one of the window.
in the openFile function of the AppleDelegate:
let storyboard = NSStoryboard(name: NSStoryboard.Name(rawValue: "Main"), bundle: nil)
let mainViewController = NSApplication.shared.mainWindow?.contentViewController as! MainViewController
mainViewController.showPdfDocument(fileData: self.fileObject!)
Reading from the Apple documentation at this link (https://developer.apple.com/documentation/appkit/nswindow/1419615-contentviewcontroller) it says 'Directly assigning a contentView value clears out the root view controller.'. Should I programmatically assign my mainView to the contentView to automatically set my mainViewController as the initial controller?
I have also found that i can set my controller as the initial controller from IB as you can see form this image:
The ideal thing i want to do is to leave the contentViewController as the initial viewController (the default setting in IB) and then instantiate my mainViewController to update the pdfView. The problem is that with instantiation, the new viewController has everything to nil(IBOutlet, vars etc..). Is it the downcasting from contentViewController to mainViewController a good approach to achieve this task?

Swift: Change ViewController after Authentication from RESTful API

I have a rails api set up and one test user. I can login/logout view the api.
I have a tab app set up, and I have a LoginViewController set up to authenticate into that. My view controller (basically) looks like the below code. This doesnt work. However, the second block of code does work
I am guessing I am calling something wrong.. The first block executes and then crashes with a *** Assertion failure in -[UIKeyboardTaskQueuewaitUntilAllTasksAreFinished].. I've been spinning my wheels for hours.. tried looking into segues.. etc. Anything would help!!!
//DOES NOT WORK //
class LoginViewController: UIViewController {
#IBOutlet weak var email: UITextField!
#IBOutlet weak var password: UITextField!
#IBAction func Login(sender: AnyObject) {
func switchToTabs() {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewControllerWithIdentifier("TabBarController") as! UITabBarController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
}
let authCall = PostData()
var authToken = ""
authCall.email = email.text!
authCall.password = password.text!
authCall.forData { jsonString in
authToken = jsonString
if(authToken != "") {
switchToTabs()
}
}
}
// SAME CLASS THAT DOES WORK//
class LoginViewController: UIViewController {
#IBOutlet weak var email: UITextField!
#IBOutlet weak var password: UITextField!
#IBAction func Login(sender: AnyObject) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let tabBarController = storyboard.instantiateViewControllerWithIdentifier("TabBarController") as! UITabBarController
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
appDelegate.window?.rootViewController = tabBarController
}
My question is, why can I seemingly navigate to a new view when I don't use my api, but when I do receive my auth token.. I can't log in.
Please excuse the ugly code.. this is my first day with swift
Everytime you every do anything that changed something about the UI inside of a closure or anything that could even possibly be off the main queue, enclose it in this statement.
dispatch_async(dispatch_get_main_queue()) {
//the code that handles UI
}
This includes all code that segues inside a closure like the one you have
authCall.forData { jsonString in
authToken = jsonString
if(authToken != "") {
switchToTabs()
}
}
If you write it like so it would work
authCall.forData {
jsonString in
authToken = jsonString
if(authToken != "") {
dispatch_async(dispatch_get_main_queue()) {
switchToTabs()
}
}
}
then call your segue in switchToTabs()
If you arn't sure what queue you are on, it won't hurt to do it. As you become more familiar with scenarios that take you off the main queue, you will realize when to use it. Anytime you are in a closure and dealing with UI its a safe bet that you could leave. Obviously anything asynchronous would cause it as well. Just place your performSegueWithIdentifier inside of it and it should work.
If you make a segue on the storyboard from the view controller attatched to LoginViewController, then you click the segue you can give it an identifier. Then you just call performSegueWithIdentifier("identifierName", sender: nil) and none of the other code in switchToTabs() is necessary.
If you want to continue down the route you are taking to summon the View Controller, then I think what you are missing is these lines.
Use this below your class outside of it.
private extension UIStoryboard {
class func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) }
class func tabBarController() -> UITabBarController? {
return mainStoryboard().instantiateViewControllerWithIdentifier("TabBarController") as? UITabBarController
}
}
Then change your switchToTabs() function out for this
func switchToTabs() {
let tabBarController = UIStoryboard.tabBarController()
view.addSubview(tabBarController!.view)
//line below can be altered for different frames
//tabBarController!.frame = self.frame
addChildViewController(tabBarController!)
tabBarController!.didMoveToParentViewController(self)
//line below is another thing worth looking into but doesn't really correlate with this
//self.navigationController!.pushViewController(tabBarController!, animated: true)
}
I added in a couple comments for you to look into if you like. I believe this will do the job, but keep in mind that the direction you are headed doesn't remove the previous screen from memory. It is technically still sitting behind your new screen. I wouldn't ever recommend this for what you are trying to do, but it might not hurt if you are building something simplistic.

How can I call a view controller function from an iOS AppDelegate?

I am getting a call from a remote push notification, which includes a path to an image, which I want to display.
The AppDelegate is:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
// get url from notification payload (shortened version)
let url = alert["imgurl"] as? NSString
let vc = ViewController()
vc.loadImage(url as String) // cannot find the imageView inside
}
...and the view looks like this:
func loadImage(url:String) {
downloadImage(url, imgView: self.poolImage)
}
// http://stackoverflow.com/a/28942299/1011227
func getDataFromUrl(url:String, completion: ((data: NSData?) -> Void)) {
NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: url)!) { (data, response, error) in
completion(data: NSData(data: data!))
}.resume()
}
func downloadImage(url:String, imgView:UIImageView){
getDataFromUrl(url) { data in
dispatch_async(dispatch_get_main_queue()) {
imgView.image = UIImage(data: data!)
}
}
}
I'm getting an exception saying imgView is null (it is declared as #IBOutlet weak var poolImage: UIImageView! and I can display an image by calling loadImage("http://lorempixel.com/400/200/") from a button click.
I found a couple of related answers on stackoverflow (one is referenced above) but none work.
If you want to show a new view controller modally, then look into rach's answer further.
If you're ViewController is already on screen, you will need to update it to show the information you need.
How you do this will depend on your root view controller setup. Is your view controller embedded in an UINavigationController? If so then try this:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if let rootViewController = window?.rootViewController as? UINavigationController {
if let viewController = rootViewController.viewControllers.first as? ViewController {
let url = alert["imgurl"] as? NSString
viewController.loadImage(url as String)
}
}
}
If it is not, then try this:
func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) {
if let rootViewController = window?.rootViewController as? ViewController {
let url = alert["imgurl"] as? NSString
rootViewController.loadImage(url as String)
}
}
I believe at the point of time where you are calling
let vc = ViewController()
it has yet to be instantiated. Since you are using #IBOutlet for your UIImageView, I am assuming that you actually have that UIViewController.
Maybe it could be corrected by this:
let vc = self.storyboard?.self.storyboard?.instantiateViewControllerWithIdentifier("ViewControllerStoryboardID")
vc.loadImage("imageUrl")
self.presentViewController(vc!, animated: true, completion: nil)
Let me know if it works. :)
EDIT: You could call an instance of storyboard via this method:
self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
var storyboard = UIStoryboard(name: "Main", bundle: nil)
var vc = storyboard.instantiateViewControllerWithIdentifier("ViewController") as! UIViewController
vc.loadImage("imageUrl")
self.window?.rootViewController = vc
self.window?.makeKeyAndVisible()
By calling #IBOutlet weak var poolImage: UIImageView! you're passing the initialization of poolImage to the storyboard. Since you're initializing the view controller with let vc = ViewController(), vc will know that it is expected to have a reference to poolImage because you declared it in the class, but since you aren't actually initializing vc's view (and subviews) poolImage remains nil until the UI is loaded.
If you need to reference a UIImageView at the time of the initialization of vc, you'll have to manually instantiate it: let poolImage = UIImageView()
EDIT: Your ViewController implementation of poolImage currently looks like this:
class ViewController : UIViewController {
#IBOutlet weak var poolImage : UIImageView!
// rest of file
}
To access poolImage from AppDelegate via let vc = ViewController() you have to make the implementation look like this:
class ViewController : UIViewController {
/* ---EDIT--- add a placeholder where you would like
poolImage to be displayed in the storyboard */
#IBOutlet weak var poolImagePlaceholder : UIView!
var poolImage = UIImageView()
// rest of file
override func viewDidLoad() {
super.viewDidLoad()
/* ---EDIT--- make poolImage's frame identical to the
placeholder view's frame */
poolImage.frame = poolImagePlaceholder.frame
poolImagePlaceholder.addSubView(poolImage)
// then add auto layout constraints on poolImage here if necessary
}
}
That way it poolImage is available for reference when ViewController is created.
The reason I think is because the imageView may not have been initialized. Try the following solution.
Add another init method to your viewController which accepts a URLString. Once you receive a push notification, initialize your viewController and pass the imageURL to the new init method.
Inside the new initMethod, you can use the imageURL to download the image and set it to the imageView. Now your imageView should not be null and the error should not occur.

Pass messages and/or objects between different storyboards swift

I've two storyboards and need to pass messages and objects. I know how to do it in the same storyboard and with .xib files, but not with two different storyboards.
My code is:
var storyboard = UIStoryboard(name: "RecibosStoryboard", bundle: nil)
var controller = storyboard.instantiateViewControllerWithIdentifier("RecibosStoryboard") as! UINavigationController
self.presentViewController(controller, animated: true, completion: nil).
// If i do: var controller = storyboard.instantiateViewControllerWithIdentifier("RecibosStoryboard") as! = TableRecibosViewController -> fails ->cannot convert TablaRecibosViewController to UINavigationController
// If i do:
/* var controller = storyboard.instantiateViewControllerWithIdentifier("RecibosStoryboard") as! UINavigationController
let vartest: TablaRecibosTableViewController = TablaTablaRecibosTableViewController()
prueba.inicioPrueba(str, strPrueba2: str2) -> two objects are nill
self.presentViewController(controller, animated: true, completion: nil).
Two objects are nill*/
My second storyboard is "RecibosStoryboard" and only has 1 view who class is TablaRecibosViewController and has a contructor method:
class TablaRecibosTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tablaRecibos: UITableView!
var pruebaMansajes: String?
var pruebaMansajes2: String?
var arrayOfObjectsCellRecibos: [ObjectTableRecibos] = [ObjectTableRecibos] ()
override func viewDidLoad() {
super.viewDidLoad()
tablaRecibos.dataSource = self
tablaRecibos.delegate = self
println("Pruebas satisfactorias1 \(pruebaMansajes) \(pruebaMansajes2)")
}
func inicioPrueba(strprueba1:String, strPrueba2:String){
pruebaMansajes = strprueba1
pruebaMansajes2 = strPrueba2
}
When i execute the App or crash or print two objects = nil
I don't find the way to do. Thanks a lot.
The problem is the following "cannot convert TablaRecibosViewController to UINavigationController". Your TablaRecibosViewController is embedded inside a UINavigationController. Try the following:
if let recibosViewController = (storyboard.instantiateViewControllerWithIdentifier("RecibosStoryboard") as? UINavigationController).viewControllers.first as? TablaRecibosTableViewController {
recibosViewController.pruebaMensajes = "Hola mundo"
}
You were creating a new instance of TablaTablaRecibosTableViewController() which was not associated to the Storyboard, therefore the table view was nil and it crashed.

Resources