Swift: Perform Segue in AppDelegate.swift - swift

I've successfully integrated Google Sign-In into my iOS app in my AppDelegate.swift file, and can successfully detect a successful sign-in (by printing "success" out onto the console). The issue is that after a successful sign-in, I'm back at the Google Sign-In page when I want to be in the next screen of the app.
The AppDelegate.swift file did not recognize the performSegue function, as it is a function of the UIViewController class (please correct me if I'm wrong). To get around this, I created a global variable in the ViewController file such that the segue would be performed whenever this value would be changed:
AppDelegate.swift:
var userSignedInGlobal = "n/a"
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
// A bunch of code that implements the Google Sign in ...
print("Successfully logged into Google.", user)
userSignedInGlobal = "success"
}
And then my InitialViewController.swift file:
class InitialViewController: UIViewController, GIDSignInUIDelegate {
var userSignedIn = userSignedInGlobal {
didSet {
performSegue(withIdentifier: "segueOne", sender: self)
}
}
// A bunch of irrelevant code.
}
This did not work, and the reason I think this didn't work is that userSignedInGlobal in the InitialViewController.swift file is being passed by reference - so even when its value changes, the value of userSignedIn does not change (again, please do correct me if I'm wrong).
To get around this, I changed my InitialViewController.swift file as follows:
var userSignedIn = userSignedInGlobal {
didSet {
doSegue()
}
}
class InitialViewController: UIViewController, GIDSignInUIDelegate {
func doSegue() {
performSegue(withIdentifier: "segueOne", sender: self)
}
// A bunch of irrelevant code.
}
This gave me an error in the third line: "Use of unresolved identifier 'doSegue()' "
I do not know how to go about performing the segue when the sign in is successful. Any help will be greatly appreciated, thanks in advance.

There are a few problems here:
Global Variables in App Delegate
There are numerous answers demonstrating how to store a global variable in your AppDelegate and then subsequently reference that variable in your view controllers, e.g. getting a reference to app delegate
Perform Segue only from Storyboard based controllers
You won't be able to use performSegue unless your current View Controller was loaded from a StoryBoard (see performSegue). This probably is not the case with whatever Google view you are loading from your AppDelegate.
Are you doing the login in the right place?
You might want to reconsider the division of duties between your AppDelegate and your initial view controller. Perhaps you are trying to do too much in the AppDelegate. Perhaps you should move some of this code (showing the Google form) to your initial VC and then either segue or reset your Navigation Controller based on the result.
Hope this helps.

Related

Swift: Reload data in UITableView element triggered in an external class

I'm trying to call the reloadData() method to update a table view in an external class (different view controller), using the method viewDidDisappear().
I can already update the table view when the view where it is located is loaded or appears:
class OrderHistoryController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet var orderTable: UITableView!
//called when order table is loaded or appears
override func viewDidLoad() {
super.viewDidLoad()
self.orderTable.delegate = self
self.orderTable.dataSource = self
self.orderTable.reloadData()
}
override func viewDidAppear(_ animated: Bool) {
self.orderTable.delegate = self
self.orderTable.dataSource = self
self.orderTable.reloadData()
}
// ...
}
But I want the table to be reloaded when another view disappears.
class OrderDocumentationController: UIViewController {
override func viewDidDisappear(_ animated: Bool) {
OrderHistoryController().orderTable.reloadData()
return
}
// ...
}
I get the fatal error:
Unexpectedly found nil while implicitly unwrapping an Optional value.
Guess it is just a rookie mistake. Any ideas? Thank you in advance!
When you call OrderHistoryController().orderTable.reloadData(), this will create a new OrderHistoryController instance, that will have not orderTable outlet connected hence resulting in the crash.
There are differnt ways to achieve what you want:
Store a reference to the OrderHistoryController instance and use this
Maybe better: Implement some custom delegation mechanism
Use the NotificationCenter to send a message that then will result in a refresh
No need to reload orderTable if OrderHistoryController appears when OrderDocumentationController disappears. Since, self.orderTable.reloadData() is called in OrderHistoryController::viewDidAppear()
UPDATE:
A better approach would be to have OrderDocumentationController provide a block which is called when the modal controller has completed.
So in OrderDocumentationController, provide a block property say with the name onDoneBlock.
In OrderHistoryController you present as follows:
In OrderHistoryController, create OrderDocumentationController
Set the done handler for OrderDocumentationController as: OrderDocumentationController.onDoneBlock={[OrderDocumentationController dismissViewControllerAnimated:YES completion:nil]};
Present the OrderDocumentationController controller as normal using [self OrderDocumentationController animated:YES completion:nil];
In OrderDocumentationController, in the cancel target action call self.onDoneBlock();
The result is OrderDocumentationController tells whoever raises it that it is done. You can extend the onDoneBlock to have arguments which indicate if the modal completed, cancelled, succeeded etc.

Xcode: how to create instances of views and pass info to them?

I'm trying to create a MacOS app that plays audio or video files. I've followed the simple instructions on Apple's website here
But I want to use the File > Open menu items to bring up an NSOpenPanel, and pass that to the View Controller.
So presumably, the Open action should be in the AppDelegate, as the ViewController window might not be open.
And then pass the filename to a new instance of the ViewController window.
Is that right? If so, how do I "call" the View from AppDelegate?
Here's the AppDelegate:
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
#IBAction func browseFile(sender: AnyObject) {
let dialog = NSOpenPanel();
if (dialog.runModal() == NSModalResponseOK) {
let result = dialog.url // Pathname of the file
if (result != nil) {
// Pass the filepath to the window view thing.
} else {
// User clicked on "Cancel"
return
}
}
}
and here's the ViewController:
class ViewController: NSViewController {
#IBOutlet weak var playerView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
// Get the URL somehow
let player = AVPlayer(url: url)
playerView.player = player
}
There are some details not disclosed in your question, but I believe I can provide the proper answer still.
You can call NSOpenPanel from AppDelegate, nothing wrong with that. Just note that user may cancel the dialog and how to handle that situation.
Considering the view the best thing is to create WindowController that is connected to the ViewController (it is like that by default) in the Storyboard, then access it from the code using NSStoryBoard.instantiateController(withIdentifier:), and then use its window property with something like window.makeKeyAndOrderFront(self) . If you have NSWindow or NSWindowController class in your code then you should initialize the class in the code and again make window key and front.

strange optional error in Swift

Okay, so basically I am following a tutorial on udemy on how to create a chat with Backendless and Firebase. However, I prefer not to use Backendless, because I don't want to rely on 2 providers - so I want to stick to Firebase only. Therefore, I am currently converting my code to Firebase.
I have a view controller that displays a unique page for each UID - from a database that I have. The UID is stored as a String, and is assigned upon a segue from another table view controller (this works fine). After that, I fetch the data that I want from the user, with the UID. I have a "Start Chat" button that is supposed to create a new chat.
In this tutorial, the tutor has set a protocol (delegate) that is triggering another function from another view controller. This is what it looks like:
protocol ChooseUserDelegate {
func createChatroom(withUser: String)
}
var delegate: ChooseUserDelegate!
and in my chat #IBAction, I have this code:
#IBAction func StartChat(sender: AnyObject) {
let userID = uid
if let theId = userID as? String {
delegate.createChat(String(theId))
}
}
(The code above is all in the same VC.).
In another view controller, where the createChat() function is stored, is the following code:
class AnotherVC UIViewController, UITableViewDelegate, UITableViewDataSource, ChooseUserDelegate{
func createChat(withUser: String) {
print(withUser)
}
}
The problem is that I can't get to call createChat(), because of an optional error (unwrapping) on the delegate.createChat(String(theId)).
Edit: Even with a "" input, I get an error. I am really confused now. Is it something wrong with my delegate?
The only part of your code that is optional is delegate (because you correctly unwrapped userID). Therefore, the error must be due to delegate being nil. Make sure that you set delegate before calling StartChat().
The line var delegate: ChooseUserDelegate! does not initialize a delegate. When you write ChooseUserDelegate! you are only defining the type of the delegate variable. It is automatically set to nil. To initialize a new instance of ChooseUserDelegate you would need to write something like:
var delegate: ChooseUserDelegate! = ChooseUserDelegate()
There are a few other ways you could clean up your code. Method names should be llamaCase, not CamelCase, so you should rename StartChat() to startChat() (be sure to reconnect in interface builder). The body of that method has three different names for the same variable, uid. See how simple it could be:
#IBAction func startChat(sender: AnyObject) {
if let uid = uid as? String {
delegate.createChat(uid)
}
}
if let theId = userID {
delegate.createChat(String(theId))
}

Swift-passing global variables to the #IBAction function?

I am trying to allow the user to select a .png file to open by clicking file on the menu bar of the application, and then open a Microsoft Word file in the same way.
The problem is it appears that #IBAction func SelectFileToOpen(sender: NSMenuItem) {} cannot access global variables, or set them, and seems completely independent from the rest of the code
here is my code designed to demonstrate how the method can't read global variables:
//
// AppDelegate.swift
// Swift class based
//
// Created by ethan sanford on 2015-01-16.
// Copyright (c) 2015 ethan D sanford. All rights reserved.
//
import Cocoa
#NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(aNotification: NSNotification) {
// Insert code here to initialize your application
}
func applicationWillTerminate(aNotification: NSNotification) {
// Insert code here to tear down your application
}
var myURL=NSURL(fileURLWithPath: "")
#IBAction func btnConcat(sender: NSButton) {
myURL = NSURL(fileURLWithPath: "///Users/ethansanford/Desktop/BigWriting.png")
var say_something = "set URL button clicked"
print(say_something);
print(myURL)
}
#IBAction func SelectFileToOpen(sender: NSMenuItem) {
var say_something = "Menu bar, file-open clicked:"
print(say_something);
print(myURL);
}
#IBAction func communicate(sender: AnyObject) {
var say_something = "communicate button clicked:"
print(say_something);
print(myURL);
}
}
Here is the NSlog produced from this code. Notice that the URL button and the commincate button methods can share the myURL variable, but the file open button seems unable to:
URL button clickedOptional(file://///Users/ethansanford/Desktop/BigWriting.png)
communicate button clicked:Optional(file://///Users/ethansanford/Desktop/BigWriting.png)Menu bar
file-open clicked:nil
communicate button clicked:Optional(file://///Users/ethansanford/Desktop/BigWriting.png)
I need the myURL variable to be able to be used in all three methods. This is necessary for later when I need these methods to communicates so I can take the users selection and display it in an image well. Thanks for any help you can provide. I believe the problem is something specific to the file button in the menu bar.
Can anyone explain to me how to get around this problem?
In your code myURL is an instance variable that will be created within the app delegate. I wonder if you have oversimplified your code sample.
Having said that it should be accessible from the instance methods of the app delegate but having IBAction methods in the AppDelegate rather than in UI code seems like an odd choice (I've never tried it).

performSegue after completion handler

ANSWER BELOW
Im facing a little issue that you may help me with.
the app Im working on allows you to request for content based on your location.
the first ViewController is somewhat a form that grab your location / a specified location + some other information to target specific answers.
I need to perform a segue to pass the "question" variables to my second ViewController where I load "answers" with a query based on the question details.
What is causing me trouble is that, whenever the question is geolocalized, I can't retrieve the information using prepareForSegue because it doesn't wait for the geoPoint to be made (completed).The second controller display my latitude and longitude as nil.
I see that I can call the "prepareForSegue" method using "perfomSegueWithIdentifier", and retrieve the information in my second view controller but it perform the segue twice... How can I trigger the segue only when Im ready but using the prepareForSegue data parameter I need to preserve?
Is there a way to pass variable from one controller to another using performSegue?
Any help would be awesome.
Also, while I don't think the code is relevant for my question, here is the code I use.
geoPointing method
#IBAction func doPostQuestion(sender: UIButton) {
var thereQ:PFObject = PFObject(className: "tquestion")
if(somewhereLabel.text == "my location"){
println("Location is geolocalized")
PFGeoPoint.geoPointForCurrentLocationInBackground {
(geoPoint: PFGeoPoint!, error: NSError!) -> Void in
if error == nil {
self.geoLati = geoPoint.latitude as Double
self.geoLong = geoPoint.longitude as Double
self.performSegueWithIdentifier("goto_results", sender:self) // call prepareForSegue when ready but implies to have a segue done on click... (performed twiced)
}
}
}
self.navigationController?.popToRootViewControllerAnimated(true)
}
prepareForSegue
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "goto_results"){
// Get Label
let theDestination = (segue.destinationViewController as displayAnswersViewController)
theDestination.lat = self.geoLati
theDestination.lng = self.geoLong
}
}
ANSWER SOLUTION:
As suggested, to solve this problem you just need to create your segue from your viewController1 to your viewController2 and not from a button. This way you can trigger prepareForSegue programatically using the "performSegue" method that will call prepareForSegue anyway.
To solve this problem you just need to create your segue from your viewController1 to your viewController2 and not from a button. This way you can trigger prepareForSegue programatically using the "performSegue" method that will call prepareForSegue anyway.