Error Editing Google Sheets Swift - swift

I am trying to edit google sheets from within swift. I used the original Quickstart tutorial by google, but since that is for reading a spreadsheet, I had to slightly edit it for writing to a spreadsheet.
//Signing in as normal from the Google Quickstart
//This function is the one I edited, to write to the spreadsheet
func listMajors() {
output.text = "Getting sheet data..."
let spreadsheetId = "**********************"
let range = "Sheet 1!A2"
let valueRange = GTLRSheets_ValueRange.init()
valueRange.values = [["Hello" as Any]]
let query = GTLRSheetsQuery_SpreadsheetsValuesUpdate.query(withObject: valueRange, spreadsheetId: spreadsheetId, range: range)
query.valueInputOption = "USER_ENTERED"
service.executeQuery(query, delegate: self, didFinish: #selector(displayResultWithTicket(ticket:finishedWithObject:error:)))
}
func displayResultWithTicket(ticket: GTLRServiceTicket, finishedWithObject result : GTLRSheets_ValueRange, error : NSError?) {
if let error = error {
showAlert(title: "Error", message: error.localizedDescription)
return
}
print(result)
}
It seemed to work fine for the reading spreadsheet, but it is not working for writing to spreadsheet. The app launches, then i get an error alert with this:

Sorry to add solution in objective-C, but i think you will be able to manage conversion
I just have the same problem today.
The solution is simple, when initializing the GIDSignIn
// Configure Google Sign-in.
GIDSignIn* signIn = [GIDSignIn sharedInstance];
signIn.delegate = self;
signIn.uiDelegate = self;
signIn.scopes = [NSArray arrayWithObjects:kGTLRAuthScopeSheetsSpreadsheets, nil];
[signIn signInSilently];
Note the scope value. In the quickStart tutorial, kGTLRAuthScopeSheetsSpreadsheetsReadonly is used.
Romain

Related

Access user's first name and other information in firebase using Swift

I am building my first ever IOS app with the help of online resources. I was able to build the signup and login screens of the app and connect it to firebase and store user's information on there. However, I want to get (retrieve) user's first name from the database when they log in and display that, and I found a piece of code that successfully does that but it is very slow, everything else in the page loads before it, so it is not really an ideal solution. I was wondering if there was another way to achieve what I want to do.
Code that checks if user's is signed in and gets its first name:
func CheckIfUserIsSignedIn() {
let db = Firestore.firestore()
if let userId = Auth.auth().currentUser?.uid {
let userName = db.collection("users").getDocuments() { (snapshot, error) in
if let error = error {
self.errorLabel.textColor = UIColor.red
self.errorLabel.text = "Error getting documents: \(error)"
}
else {
for document in snapshot!.documents {
if let firstUserDoc = snapshot?.documents.first {
let welcomeName = firstUserDoc["first_name"] as! String
self.errorLabel.text = "Hey, \(welcomeName) welcome!"
}
}
} //end else
}
} //end if
else {
//User is not logged in
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "login")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
} //end CheckIfUserIsSignedIn method`
There are a few ways you can do this but just one thing I want to point out is that when you check if the user has logged in with if let userId = Auth.auth().currentUser?.uid you then proceed to retrieve every user document with let userName = db.collection("users").getDocuments() which is probably making it much more slower than it needs to be. And this will get slower the more popular your app becomes because downloading more users takes more time. This is an easy fix by just adding one small thing:
let userName = db.collection("users").document(userId).getDocument()
This only gets 1 document instead of all.
Also right after that you loop through each document you've retrieved and perform
if let firstUserDoc = snapshot?.documents.first {
let welcomeName = firstUserDoc["first_name"] as! String
self.errorLabel.text = "Hey, \(welcomeName) welcome!"
}
This block is run snapshot!.documents.count (Number of users you have in your app) amount of times which again seems unnecessary as it does the same thing each iteration. Remove the loop and doing it 1 time will be so much faster.
This is how your code should look after:
if let userId = Auth.auth().currentUser?.uid {
db.collection("users").document(userId).getDocument { docSnapshot, error in
if let error = error {
errorLabel.textColor = UIColor.red
errorLabel.text = "Error getting documents: \(error)"
} else {
let welcomeName = docSnapshot!.get("first_name") as! String
errorLabel.text = "Hey, \(welcomeName) welcome!"
}
}
} //end if
// ...Other code
This should work but if you want an even faster way to do this and don't use the Auth.auth().currentUser!.displayName property then you can store their first name in that and simply reduce your code to:
if let userFirstName = Auth.auth().currentUser?.displayName! {
errorLabel.text = "Hey, \(userFirstName) welcome!"
} else {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vc = storyboard.instantiateViewController(identifier: "login")
vc.modalPresentationStyle = .overFullScreen
present(vc, animated: true)
}
^ This would be ideal if your app refers to all users by their first names instead of their usernames (plus it'll show up in verification emails as well)
One last thing I'd like to mention is in your original post I don't understand how you were guaranteeing the first document of the snapshot to be the user you want. If it's an old user using the app then someone else's name would probably come up as newer users will be at the top of the list. As this is your first app I want to stress the importance of writing tests for your functions. Be sure to read up on Unit Tests and UI Tests (mainly Unit Tests for your purposes), they really make a big difference. It's not too hard to learn either. I remember when I first started I avoided them like the plague because I thought they took too much time. But in the long run they save you thousands of hours by making your code as bug free as possible and even help structuring your code better by making it more modular!
Hope this helps and best of luck with your first app!

How to get login username with AWS Mobile-hub SDK

I've been working for a while on the login part of my app. I'm trying to use ASW Mobile Hub for this matter. I found a way to get it work with the different providers I need: my own user pool, FB and Google.
The problem is that I've been searching here and all over the AWS documentation trying to find the way to get user data (Username and some othe user data like picture, email and so on). I can get it if I'm using the FBSDK directly (usingFBSDKGraphRequest) but I don't know how to do it if the user choose to login in my cognito-user-pool. Also I cannot see what provider the user used once succeeded.
I can find some other ways to get that, but using the old SDK o directly Cognito calls and initially is not what I need. Here's the code I'm using to present the login window:
override func viewDidLoad() {
super.viewDidLoad()
if !AWSSignInManager.sharedInstance().isLoggedIn {
presentAuthUIViewController()
}
}
func presentAuthUIViewController() {
let config = AWSAuthUIConfiguration()
config.enableUserPoolsUI = true
config.addSignInButtonView(class: AWSFacebookSignInButton.self)
config.addSignInButtonView(class: AWSGoogleSignInButton.self)
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider:
AWSSignInProvider, error: Error?) in
if error == nil {
// SignIn succeeded.
} else {
// end user faced error while loggin in, take any
required action here.
}
})
}
So, the question is, how can I get the relevant user info, once the signin is succeeded?
If the user used cognito login, you can use the below code to get the username.
let identityManager = AWSIdentityManager.default()
let identityUserName = identityManager.identityProfile?.userName
For retrieving the provider once user succeeds, keep it in the session as below
func onLogin(signInProvider: AWSSignInProvider, result: Any?,
authState: AWSIdentityManagerAuthState, error: Error?) {
let defaults = UserDefaults.standard
defaults.set(signInProvider.identityProviderName, forKey:
"identityProviderName")
}
Hope this answer helps.
Updated Code to get Username:
let pool = AWSCognitoIdentityUserPool.init(forKey: "CognitoUserPools")
let username = pool.currentUser()?.username
I've been working on a workaround till I sort this out in a more elegant way. I guess that I need to go deeper in Cognito's understanding. But the fact is even the sample provided by Amazon doesen't show the User's Name...
Sample Amazon app screen
So, in the meantime, I modified the source code of the Cognito library AWSUserPoolsUIOperations to send me the data directly to my app, on a message:
#implementation AWSUserPoolsUIOperations
-(void)loginWithUserName:(NSString *)userName
password:(NSString *)password
navigationController:(UINavigationController *)navController
completionHandler:(nonnull void (^)(id _Nullable, NSError *
_Nullable))completionHandler {
self.userName = userName;
NSDictionary* userInfo = #{#"username": self.userName};
[[NSNotificationCenter defaultCenter]
postNotificationName:#"UsernameNotification"
object:nil userInfo:userInfo];
And then just getting the message in the app and storing the value.
#objc private func TestNotification(_ notification: NSNotification){
if let dict = notification.userInfo as NSDictionary? {
if let username = dict["username"] as? String {
appEstats.username = username
defaults.set(username, forKey: sUserName)
}
}
}
As I said is not the solution but in the meantime it works.

issue receiving outcomes when sending text to wit.ai

I'm using the following to send text to wit.ai through a button press function:
#IBAction func searchButton(sender: AnyObject) {
searchQueryText = searchTextInput.text!
if searchQueryText != "" {
wit.interpretString(searchQueryText, customData: nil)
}
func interpretString(string: String, customData: AnyObject) {
}
this works fine as the text is sent to wit.ai. However I get no response from wit.ai back to the app. I can get the response fine if a microphone is used, just not text. I have tried calling the witDidGraspIntent function to force it to run on button press, but I can't work out what I should use in the 'outcomes' parameter. Can anybody help on this? I'm not sure if there is a different way to run the function after button press? This is the function:
func witDidGraspIntent(outcomes: [AnyObject]!, messageId: String!, customData: AnyObject!, error e: NSError!) {
if ((e) != nil) {
print("\(e.localizedDescription)")
return
}
let outcomes : NSArray = outcomes!
let firstOutcome : NSDictionary = outcomes.objectAtIndex(0) as! NSDictionary
if let intent = firstOutcome.objectForKey("intent") as? String {
searchResultsIntent = intent
}
if searchResultsIntent == "searchIntent" {
intentLabel.text = "\(searchResultsIntent)"
print(outcomes[0])
} else {
intentLabel.text = "I'm sorry, I did not understand that."
}
}
here is the documentation for wit.ai: https://wit.ai/docs/ios/4.0.0/api
any assistance is greatly appreciated!
cheers.
Wit sdk gives a sharedInstance (singleton) for users to work on, so you have initiate it like -:
Wit.sharedInstance().accessToken = "TOKEN"
Wit.sharedInstance().delegate = self
and invoke the interpretString function using the sharedInstance i.e.
Wit.sharedInstance().interpretString(text, customData: nil)

Create CSV file in Swift and write to file

I have an app I've made that has a UITableView with todoItems as an array for it. It works flawlessly and I have an export button that creates a CSV file from the UITableView data and emails it out:
// Variables
var toDoItems:[String] = []
var convertMutable: NSMutableString!
var incomingString: String = ""
var datastring: NSString!
// Mail alert if user does not have email setup on device
func showSendMailErrorAlert() {
let sendMailErrorAlert = UIAlertView(title: "Could Not Send Email", message: "Your device could not send e-mail. Please check e-mail configuration and try again.", delegate: self, cancelButtonTitle: "OK")
sendMailErrorAlert.show()
}
// MARK: MFMailComposeViewControllerDelegate Method
func mailComposeController(controller: MFMailComposeViewController, didFinishWithResult result: MFMailComposeResult, error: NSError?) {
controller.dismissViewControllerAnimated(true, completion: nil)
}
// CSV Export Button
#IBAction func csvExport(sender: AnyObject) {
// Convert tableView String Data to NSMutableString
convertMutable = NSMutableString();
for item in toDoItems
{
convertMutable.appendFormat("%#\r", item)
}
print("NSMutableString: \(convertMutable)")
// Convert above NSMutableString to NSData
let data = convertMutable.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
if let d = data { // Unwrap since data is optional and print
print("NSData: \(d)")
}
//Email Functions
func configuredMailComposeViewController() -> MFMailComposeViewController {
let mailComposerVC = MFMailComposeViewController()
mailComposerVC.mailComposeDelegate = self
mailComposerVC.setSubject("CSV File Export")
mailComposerVC.setMessageBody("", isHTML: false)
mailComposerVC.addAttachmentData(data!, mimeType: "text/csv", fileName: "TodoList.csv")
return mailComposerVC
}
// Compose Email
let mailComposeViewController = configuredMailComposeViewController()
if MFMailComposeViewController.canSendMail() {
self.presentViewController(mailComposeViewController, animated: true, completion: nil)
} else {
self.showSendMailErrorAlert() // One of the MAIL functions
}
}
My question is how do I create the same CSV file, but instead of emailing, save it to file? I'm new to programming and still learning Swift 2. I understand that the section of code (data!, mimeType: "text/csv", fileName: "TodoList.csv") creates the file as an attachment. I've looked online for this and trying to understand paths and directories is hard for me. My ultimate goal is to have another UITableView with a list of these 'saved' CSV files listed. Can someone please help? Thank you!
I added the following IBAction to my project:
// Save Item to Memory
#IBAction func saveButton(sender: UIBarButtonItem) {
// Convert tableView String Data to NSMutableString
convertMutable = NSMutableString();
for item in toDoItems
{
convertMutable.appendFormat("%#\r", item)
}
print("NSMutableString: \(convertMutable)")
// Convert above NSMutableString to NSData
let data = convertMutable.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
if let d = data { // Unwrap since data is optional and print
print("NSData: \(d)")
}
let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
func writeToFile(_: convertMutable, path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws {
}
}
I was struggling to find a decent simple answer to this for ages.
Here is the best way that I've found to create a csv file and even the directory you want it to be it and write to it.
//First make sure you know your file path, you can get it from user input or whatever
//Keep the path clean of the name for now
var filePath = "/Users/Johnson/Documents/NEW FOLDER/"
//then you need to pick your file name
let fileName = "AwesomeFile.csv"
//You should probably have some data to put in it
//You can even convert your array to a string by appending all of it's elements
let fileData = "This,is,just,some,dummy,data"
// Create a FileManager instance this will help you make a new folder if you don't have it already
let fileManager = FileManager.default
//Create your target directory
do {
try fileManager.createDirectory(atPath: filePath!, withIntermediateDirectories: true, attributes: nil)
//Now it's finally time to complete the path
filePath! += fileName!
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
//Then simply write to the file
do {
// Write contents to file
try fileData.write(toFile: filePath!, atomically: true, encoding: String.Encoding.utf8)
print("Writing CSV to: \(filePath!)")
}
catch let error as NSError {
print("Ooops! Something went wrong: \(error)")
}
PS. Just noticed that question is from year ago, but I hope it helps a struggling newbie like myself when they inevitably stumble upon it like I did.
convertMutable can be easily written to disk with either fun writeToFile(_ path: String, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws or func writeToURL(_ url: NSURL, atomically useAuxiliaryFile: Bool, encoding enc: UInt) throws. All you have to do is create a path or URL to write the string out to. If you are using iCloud things will be more challenging but for locally stored files you can use let path = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString to get the root path of the documents directory.
Update: Based on you first comment below here is some added info:
The first issue is that it appears as though you are looking for code you can just paste int your project without really understanding what it does. My appologies if I'm wrong, but if I'm right this is not a good route to take as you will have many issues down the road when things change.
At the bottom of your last code section you are trying to create a function inside a function which is not going to do what you want. The above mentioned functions are the declarations of two NSString functions not functions that you need to create. As NSMutableString is a subclass of NSString you can use those functions on your convertMutable variable.
Another thing you need to deal with is creating a name for the file you want to save, currently you have pasted in the line above that gets the Documents directory but does not have a file name. You will need to devise a way to create a unique filename each time you save a CSV file and add that name to the end of path. Then you can use writeToFile… or writeToURL… to write the string to the desired location.
If you find you don't fully comprehend the code you are adding then consider getting a book or finding classes about Swift (Coursera.org has a class that may be of use). There are plenty of resources out there learn the basics of software development and Swift, it will take effort and time but it will be worth it in the end if this is something you want to pursue.

MKLocalSearch on Swift init() doesn't work

i am trying to use MKLocalSearch Api in Swift. But I can't get it to work. The error is coming from
var search:MKLocalSearch = MKLocalSearch.init(request)
I read the documentation, and it state the method name is init(request:) I am not sure what i did wrong. please advice. :)
var request = MKLocalSearchRequest()
request.naturalLanguageQuery = searchTextFiled.text
println(searchTextFiled.text)
request.region = self.mapView.region //need to define region later
var search:MKLocalSearch = MKLocalSearch.init(request)
search.startWithCompletionHandler {
(response:MKLocalSearchResponse!, error:NSError!) in
if !error {
var placemarks:NSMutableArray = NSMutableArray()
for item in response.mapItems {
placemarks.addObject(placemarks)
}
self.mapView.removeAnnotations(self.mapView.annotations)
self.mapView.showAnnotations(placemarks, animated: true)
} else {
}
}
Change this line:
var search:MKLocalSearch = MKLocalSearch.init(request)
To:
var search:MKLocalSearch = MKLocalSearch.init(request: request)
It's made a little confusing by the naming involved in this case - the first part, request: is a label that tells Swift which init function to call - in this case, it's mapping back to the Objective-C initWithRequest: method.
The second part, request is your MKLocalSearchRequest variable that you're passing to the init function. So the Objective-C equivalent is this:
MKLocalSearch *search = [[MKLocalSearch alloc] initWithRequest:request];
There's a whole chapter titled "Initialization" in Apple's The Swift Programming Language book, available on the iBooks store and as a PDF here, albeit with worse (bordering on terrible) formatting in the PDF.
That's incorrect syntax for init. They're implied by Swift, so you'd use
var x = MKLocalSearch(request: request)
not
var x = MKLocalSearch.init(request)