"fatal error: unexpectedly found nil while unwrapping an Optional value" in Standardized Social Media Sharing Method - swift

I am trying to create a standardized social media sharing method inside my SocialMediaSharingDelegate. Specifying an individual social media service like I do in the facebookShare method works perfectly fine. So to extend this I created an enum with the possible service types and the shareOn method accepts any valid service name to perform sharing.
It seems straight forward. However, when I run the code and click on a button that calls the shareOn method I get a runtime error. The error "fatal error: unexpectedly found nil while unwrapping an Optional value" occurs on the line let serviceViewController... right after the guard statement.
It also works fine if I replace socialMediaService.associatedService() with SLServiceTypeFacebook on that line which is strange because socialMediaService.associatedService() is used without issue in the guard statement.
I can just create separate functions for each service for now but that is not ideal in terms of code reuse.
import Foundation
import UIKit
import Social
enum SocialMediaEnum : String {
case Twitter = "SLServiceTypeTwitter"
case Facebook = "SLServiceTypeFacebook"
// This internal enum function returns the SLServiceType associated with the current enum value
func associatedService() -> String {
var serviceName: String
switch self {
case .Twitter:
serviceName = "SLServiceTypeTwitter"
case .Facebook:
serviceName = "SLServiceTypeFacebook"
}
return serviceName
}
}
class socialMediaSharing : SocialMediaSharingDelegate {
func shareOn(let socialMediaService: SocialMediaEnum, sender: subclassedUIButton?, currentViewController: UIViewController) {
guard SLComposeViewController.isAvailableForServiceType(socialMediaService.associatedService()) else {
let alert = UIAlertController(title: "Accounts", message: "Please login to a Twitter account to tweet.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
currentViewController.presentViewController(alert, animated: true, completion: nil)
return
}
let serviceViewController :SLComposeViewController = SLComposeViewController(forServiceType: socialMediaService.associatedService())
if let imageUrl = sender?.urlString {
if let url:NSURL = NSURL(string: imageUrl as String) {
serviceViewController.addURL(url)
}
}
currentViewController.presentViewController(serviceViewController, animated: true, completion: nil)
}
func facebookShare(sender: subclassedUIButton?, currentViewController: UIViewController) {
guard SLComposeViewController.isAvailableForServiceType(SLServiceTypeFacebook) else {
let alert = UIAlertController(title: "Accounts", message: "Please login to a Facebook account to share.", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
currentViewController.presentViewController(alert, animated: true, completion: nil)
return
}
let shareToFacebook :SLComposeViewController = SLComposeViewController(forServiceType: SLServiceTypeFacebook)
if let imageUrl = sender?.urlString {
if let url:NSURL = NSURL(string: imageUrl as String) {
shareToFacebook.addURL(url)
}
}
currentViewController.presentViewController(shareToFacebook, animated: true, completion: nil)
}
}

SLServiceTypeTwitter and SLServiceTypeFacebook are keywords so they should not have quotation marks in the switch statement. The actual values they hold are "com.apple.social.(service name)".

Related

Attempt to present <UIAlertController> on whose view is not in the window hierarchy

I've been trying to present a UIAlertController when user entered wrong password for their account, the UIAlertController is located in one separate file inside a Model group which I extend the UIViewController class to add this alert functionality to it. I also has another file inside my model group namely LogIn which I wrote all the logic behind the login process so that I can call it to my LogInVC. However, I got an error of "Attempt to present on whose view is not in the window hierarchy!" whenever the function get call inside my LogInVC. I'm trying to make my project in MVC and I know what caused this error but I just don't know how to fix it. May anyone tell me how to fix this problem?
Alert
import Foundation
import UIKit
extension UIViewController {
//MARK: - Not Enough Information Alert
func notEnoughInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "OK", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
//MARK: - Incorrect Username and Password
func wrongInfo(title: String, message: String) {
let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
let OKAction = UIAlertAction(title: "Try again", style: .default, handler: nil)
alertController.addAction(OKAction)
self.present(alertController, animated: true, completion: nil)
}
LogIn
import Foundation
import Firebase
class LogIn: UIViewController{
let db = Firestore.firestore()
//MARK: - userValidation()
func userValidation(Username:String, Password:String){
db.collection("users").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
if let snapShotDocument = querySnapshot?.documents {
for doc in snapShotDocument {
let data = doc.data()
if let username = data[C.username] as? String, let password = data[C.password] as? String {
if Username == username, Password == password {
print("Log in Successfully")
}
else {
self.wrongInfo(title: "Incorrect password", message: "Try again please")
}
}
}
}
}
}
}
}
LogInVC
import UIKit
import Firebase
class LogInVC: UIViewController {
#IBOutlet weak var emailTextField: UITextField!
#IBOutlet weak var passwordTextField: UITextField!
#IBOutlet weak var logInBtn: UIButton!
let db = Firestore.firestore()
let logIn = LogIn()
override func viewDidLoad() {
super.viewDidLoad()
//logInBtn.layer.cornerRadius = logInBtn.frame.height/5
}
#IBAction func logInBtn(_ sender: UIButton) {
if let username = emailTextField.text, let password = passwordTextField.text{
if username.isEmpty || password.isEmpty{
notEnoughInfo(title: "Not enough information", message: "Please fill in all the necessary information.")
}else{
logIn.userValidation(Username: username, Password: password) //here is where problem occured
//move to another viewcontroller
}
}
}
#IBAction func signUpBtn(_ sender: UIButton) {
let push = storyboard?.instantiateViewController(withIdentifier: C.signUpVC) as! SignUpVC
push.modalPresentationStyle = .fullScreen
present(push, animated: true, completion: nil)
}
} //ends of class
You need to first dismiss the current present alert or present controller. currently you are trying to present controller over a controller that's why it shows this error. Don't present . remove this line from self.wrongInfo(title: "Incorrect password", message: "Try again please") from LogIn.
try this and you can comment again if there is anything regarding this.

Code not finishing the function, it is ending execution halfway through

My code is as follows:
#IBAction func clicked(_ sender: Any) {
let ref = Database.database().reference()
let pass = password.text
var firpass = ""
var bool = false;
ref.child(name.text as! String).child("password").observeSingleEvent(of: .value, with: { dataSnapshot in
firpass = dataSnapshot.value as! String
if firpass == pass {
bool = true
print("in here")
}
})
print(bool)
if bool {
self.sendname = name.text!
let vc = DatabaseTableViewController(nibName: "DatabaseTableViewController", bundle: nil)
vc.finalName = self.sendname
navigationController?.pushViewController(vc, animated: true)
performSegue(withIdentifier: "username", sender: self)
} else {
let alert = UIAlertController(title: "Error", message: "Incorrect username or password", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
"in here" gets printed, but bool is never printed and the alert is showing. Why does my code not enter the if bool block and output the alert?
Data is loaded from Firebase asynchronously, since it may take a while. Instead of making your app wait for the data (which would be a bad user experience), your main code continues while the data is being loaded, and then once the data is available your closure is called.
This explains the behavior you're seeing: by the time your runs, the hasn't run yet.
the solution is as simple as it is initially confusing and annoying: any code that needs the data from the database must be inside the closure, or be called from there.
So for example:
ref.child(name.text as! String).child("password").observeSingleEvent(of: .value, with: { dataSnapshot in
firpass = dataSnapshot.value as! String
if firpass == pass {
bool = true
print("in here")
}
print(bool)
if bool {
self.sendname = name.text!
let vc = DatabaseTableViewController(nibName: "DatabaseTableViewController", bundle: nil)
vc.finalName = self.sendname
navigationController?.pushViewController(vc, animated: true)
performSegue(withIdentifier: "username", sender: self)
} else {
let alert = UIAlertController(title: "Error", message: "Incorrect username or password", preferredStyle: UIAlertController.Style.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
})
Also see:
Firebase with Swift 3 counting the number of children
Array of struct not updating outside the closure
getting data out of a closure that retrieves data from firebase (showing examples with custom callbacks and delegates)
How to reload data after all Firebase calls finished? (showing how to use a dispatch group)
Finish all asynchronous requests before loading data? (another example using a dispatch group)
Also you have to set variable bool to false when you are navigating to next view controller after login. So that you login again and if password is wrong then you can not navigate to next page and only it shows alert for wrong password.

AWS Cognito doesn't call confirmSignUp when confirming user identity in Swift with Xcode

I finally managed to get the SignUp to work, but when trying to confirm the signup, I'm reaching a problem. Here is my code:
var user: AWSCognitoIdentityUser?
#IBAction func submitButton(_ sender: Any) {
guard let confirmationCodeValue = self.codeTextField.text, !confirmationCodeValue.isEmpty else {
let confirmationAlert = UIAlertController(title: alertTitle, message: alertMessage, preferredStyle: .alert)
confirmationAlert.addAction(UIAlertAction(title: "好", style: .default, handler: {action in
print("Try again!")
}))
self.present(confirmationAlert, animated: true, completion: nil)
return
}
self.user?.confirmSignUp(self.codeTextField.text!, forceAliasCreation: true).continue({[weak self] (task: AWSTask) -> Any? in
guard let strongSelf = self else { return nil }
DispatchQueue.main.async(execute: {
print("At least this is working...")
if let error = task.error {
let confirmationFailAlert = UIAlertController(title: (error as NSError).userInfo["__type"] as? String,
message: (error as NSError).userInfo["__type"] as? String,
preferredStyle: .alert)
confirmationFailAlert.addAction(UIAlertAction(title: "好",
style: .default,
handler: {action in
}))
self?.present(confirmationFailAlert, animated: true, completion: nil)
} else {
let confirmationSuccessAlert = UIAlertController(title: self?.alertTitleConfirmationComplete,
message:self?.alertMessageConfirmationComplete,
preferredStyle: .alert)
confirmationSuccessAlert.addAction(UIAlertAction(title: "好",
style: .default,
handler: {action in
self?.dismiss(animated: true, completion: nil)
}))
self?.present(confirmationSuccessAlert, animated: true, completion: nil)
}
})
return nil
})
}
The first part of this code works fine. If I type nothing in the space, I get an alertView telling me so. However, if I type anything in the space, nothing happens. The print statement "At least this is working..." never gets called. I've been staring at this code for a couple hours now trying to figure out what's wrong, and I feel like it's probably something simple, but as of now, I could use some help!
Thanks in advance!
I assume the code block above is not the full source, but be sure the optional, user, is "unencapsulated" and equal to an actual instance of AWSCognitoIdentityUser.
If it isn't, which I am assuming it is not, confirmSignUp won't know the username, sub, or have any information on the user it is "confirming".
I would recommend logging user and be sure that username is in fact a value within user.
I believe you set it equal to that instance type in the response to your AWSCognitoIdentityUserPool class signUp:password:userAttributes:validationData: call.
Check those values that are returned in AWSCognitoIdentityUserPoolSignUpResponse.

calling function from viewcontroller - parameters error - Swift2

I am having trouble calling the tweet function. I keep getting errors with the parameters. I also tried just (AnyObject) and got
Error: argument type does not conform to expected...
I am new to swift and not sure how to get this running. Tried everything I can think of. Thank you
// from GameScene
var vc = ViewController()
vc.tweetAction(sender: AnyObject)
//error: cannot create single-element tuple with an element label
//function in View Controller below
#IBAction func tweetAction(sender: AnyObject){
if SLComposeViewController.isAvailableForServiceType(SLServiceTypeTwitter){
let tweetController = SLComposeViewController(forServiceType: SLServiceTypeTwitter)
tweetController.setInitialText("I Scored on this app")
self.presentViewController(tweetController, animated: true, completion: nil)
}
else{
let alert = UIAlertController(title: "Accounts", message: "Please log into your twitter to share", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
alert.addAction(UIAlertAction(title: "Settings", style: UIAlertActionStyle.Default, handler: { (UIAlertACtion) in
let settingsURL = NSURL(string:UIApplicationOpenSettingsURLString)
if let url = settingsURL{
UIApplication.sharedApplication().openURL(url)
}
}))
self.presentViewController(alert, animated: true, completion: nil)
}
}
You are passing a type to your method invocation instead of an instance. You also are including a label for the first parameter, which is incorrect. Try:
vc.tweetAction(self)
Try to change sender parameter type from AnyObject to UILabel
var vc = ViewController()
vc.tweetAction(yourUILabelInstance)
And don't forget to modify tweetAction function as well
#IBAction func tweetAction(sender: UILabel){
...
}

Invalid Session Token (Code: 209 Version: 1.7.4)

Every time I go to run my app the app runs fine but the console prints an error. The error is "Invalid Session Token (Code: 209 Version: 1.7.4)"
I checked Parse.com and they told me to handle this error, writing a global utility function that is called by all of my parse request error callbacks. They said i can handle the "invalid session token" error in this global function and I should prompt the user to login again so that they can obtain a new session token. However when I try to input the code in my app I get the error that I am using unresolved identifiers.
Does anyone know how to fix an Invalid Session Token error. Or how I can use the code "kPFErrorInvalidSessionToken" in my app. Any help would be greatly appreciated. (the language I am writing in is swift)
I have tried to call ParseErrorHandler.handleParseError(err) not just only from AppDelegate, but also from other view controllers and it was not worked properly. Here is solution which works from every VC:
class ParseErrorHandler {
class func handleParseError(error: NSError) {
if error.domain != PFParseErrorDomain {
return
}
switch (error.code) {
// error code 209 handling
case PFErrorCode.ErrorInvalidSessionToken.rawValue:
invalidSessionTokenHandler()
default:
break
}
}
// NOTE: User has no other option but to log out in this implementation
private class func invalidSessionTokenHandler() {
let message: String = "Session is no longer valid! Please login again!"
let alert = UIAlertController(title: nil, message: message, preferredStyle: .Alert)
var vc = UIApplication.sharedApplication().keyWindow?.rootViewController
while (vc!.presentedViewController != nil)
{
vc = vc!.presentedViewController
}
vc?.presentViewController(alert, animated: true, completion: nil)
let logoutAction = UIAlertAction(title: "OK", style: .Default, handler: {
(UIAlertAction) -> Void in
let mainStoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let exampleViewController: SignUpViewController = mainStoryboard.instantiateViewControllerWithIdentifier("SignUpViewController") as! SignUpViewController
vc?.presentViewController(exampleViewController, animated: true, completion: nil)
PFUser.logOut()
})
alert.addAction(logoutAction)
}
}
Hopefully I will save somebody's time!
If by saying "unresolved identifiers" you mean to "kPFErrorInvalidSessionToken" then you should use PFErrorCode.ErrorInvalidSessionToken.rawValue instead.
please tell me if you succeded in catching the error because all i get back is a nil NSErrorPointer.
Here is my solution, largely based on code in the Parse.com iOS developers guide.
class ParseErrorHandler {
class func handleParseError(error: NSError) {
if error.domain != PFParseErrorDomain {
return
}
switch (error.code) {
// error code 209 handling
case PFErrorCode.ErrorInvalidSessionToken.rawValue:
invalidSessionTokenHandler()
default:
break
}
}
// NOTE: User has no other option but to log out in this implementation
private class func invalidSessionTokenHandler() {
let message: String = "Session is no longer valid, you are no longer logged in"
let alert = UIAlertController(title: nil, message: message, preferredStyle: .Alert)
let presentingViewController = UIApplication.sharedApplication().keyWindow?.rootViewController
presentingViewController?.presentViewController(alert, animated: true, completion: nil)
let logoutAction = UIAlertAction(title: "OK", style: .Default, handler: {
(UIAlertAction) -> Void in
let loginViewController:UIViewController = UIStoryboard(name: "Main", bundle:
nil).instantiateViewControllerWithIdentifier("Login_2.0")
presentingViewController?.presentViewController(loginViewController, animated: true, completion: nil)
PFUser.logOut()
})
alert.addAction(logoutAction)
}
}
Above is the errorHandler class that should be used to catch all errors according to the Parse documentation (in this case I'm only showing error 209)
Here is how I catch this error in AppDelegate.swift within application function:
PFAnalytics.trackAppOpenedWithLaunchOptionsInBackground(launchOptions, block: {
(suceeded: Bool?, error: NSError?) -> Void in
if let err = error {
ParseErrorHandler.handleParseError(err)
}
})
Note that the user is technically still logged in hence why logout is called