I had this working before but now it seems to have stopped. I am trying to run various googlesheets APIS such as read/write/create. I have installed the appropriate cocoa pods:
platform :ios, '10.0'
target 'Safety_App-Prototype' do
use_frameworks!
pod 'GoogleAnalytics'
pod 'GoogleAPIClientForREST/Sheets'
pod 'GoogleAPIClientForREST/Drive'
pod 'GoogleSignIn'
pod 'SVProgressHUD'
pod 'Firebase/Core'
pod 'Firebase/Analytics'
pod 'Firebase/Auth'
pod 'Firebase/Firestore'
end
I have created a google developer console project and included all relevant information into my app. (this includes the URL type under projects -> info tab as well as in my app delegate shown below:
//
// AppDelegate.swift
// Created by Michael Szabo on 2021-01-28.
//
import UIKit
import GoogleSignIn
@main
class AppDelegate: UIResponder, UIApplicationDelegate, GIDSignInDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
GIDSignIn.sharedInstance().clientID = "FORMYEYESONLY.apps.googleusercontent.com"
GIDSignIn.sharedInstance().delegate = self
return true
}
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
return GIDSignIn.sharedInstance().handle(url as URL?,
sourceApplication: options[UIApplication.OpenURLOptionsKey.sourceApplication] as? String,
annotation: options[UIApplication.OpenURLOptionsKey.annotation])
}
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("(error.localizedDescription)")
}
return
}
// Perform any operations on signed in user here.
let userId = user.userID // For client-side use only!
let idToken = user.authentication.idToken // Safe to send to the server
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
// ...
print(fullName)
}
func sign(_ signIn: GIDSignIn!, didDisconnectWith user: GIDGoogleUser!, withError error: Error!) {
// Perform any operations when the user disconnects from app here.
print("User has disconnected")
}
// MARK: UISceneSession Lifecycle
func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
// Called when a new scene session is being created.
// Use this method to select a configuration to create the new scene with.
return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
}
func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
// Called when the user discards a scene session.
// If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
// Use this method to release any resources that were specific to the discarded scenes, as they will not return.
}
}
I have added a google signing button which allows me to sign in and asks for permission to read/write to sheets and added functions in my script to create/read/write to sheets as seen below:
// Created by Michael Szabo on 2021-02-21.
//
import GoogleAPIClientForREST
import GoogleSignIn
import UIKit
class JHA_pg1: UIViewController, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, GIDSignInUIDelegate, GIDSignInDelegate {
var rowstart:NSNumber = 0
var rowend:NSNumber = 0
var columnstart:NSNumber = 0
var columnend:NSNumber = 0
var red:NSNumber = 1
var blue:NSNumber = 1
var green:NSNumber = 1
var range1 = ""
var text1 = ""
var text2 = ""
var text3 = ""
var bordertype = "SOLID"
var borderthick:NSNumber = 3
var inbortype = "NONE"
var inborthick:NSNumber = 0
var spreadsheetId = ""
@IBOutlet weak var BeginAssessment: UIButton! //SubmitButton
let today = Date()
let formatter1 = DateFormatter()
let formatter = DateFormatter()
var date = String()
var buttontitle = String()
let defaults = UserDefaults.standard
private let scopes = [kGTLRAuthScopeSheetsSpreadsheets]
private let service = GTLRSheetsService()
//=========================================================================================================
//=========================================================================================================
override func viewDidLoad() {
super.viewDidLoad()
//=========================================================================================================
// Configure Google Sign-in.
GIDSignIn.sharedInstance().delegate = self
GIDSignIn.sharedInstance().uiDelegate = self
GIDSignIn.sharedInstance().scopes = scopes
GIDSignIn.sharedInstance().signInSilently()
}
//=========================================================================================================
//=========================================================================================================
//Submit Function
@IBAction func BeginAssessment(_ sender: Any) {
if(GIDSignIn.sharedInstance()?.currentUser != nil)
{
print("loggedIn")
}
else
{
print("not loggedIn")
}
spreadsheetId = "SOME ID"
rowstart = 0
rowend = 5
columnstart = 0
columnend = 7
unmergecell()
CreateSpreadSheet()
print(spreadsheetId)
}
//========================================================================================================
//Write To Sheet Function
func write() {
let range = range1
let updateValues = [[text1,text2,text3]]
let valueRange = GTLRSheets_ValueRange() // GTLRSheets_ValueRange holds the updated values and other params
valueRange.majorDimension = "ROWS" // Indicates horizontal row insert
valueRange.range = range
valueRange.values = updateValues
let query = GTLRSheetsQuery_SpreadsheetsValuesAppend.query(withObject: valueRange, spreadsheetId: spreadsheetId, range: range)
query.valueInputOption = "USER_ENTERED"
service.executeQuery(query) { ticket, object, error in}
}
//========================================================================================================
//Unmerge Cell Function
func unmergecell() {
let request = GTLRSheets_Request.init()
let test = GTLRSheets_GridRange.init()
test.startRowIndex = rowstart
test.endRowIndex = rowend
test.startColumnIndex = columnstart
test.endColumnIndex = columnend
request.unmergeCells = GTLRSheets_UnmergeCellsRequest.init()
request.unmergeCells?.range = test
let batchUpdate = GTLRSheets_BatchUpdateSpreadsheetRequest.init()
batchUpdate.requests = [request]
let createQuery = GTLRSheetsQuery_SpreadsheetsBatchUpdate.query(withObject: batchUpdate, spreadsheetId: spreadsheetId)
service.executeQuery(createQuery) { (ticket, result, NSError) in
}
}
}
//========================================================================================================
//Create Spreadsheet Function
func CreateSpreadSheet()
{
print("==============================================")
print("Createsheet Function")
let newSheet = GTLRSheets_Spreadsheet.init()
let properties = GTLRSheets_SpreadsheetProperties.init()
properties.title = "Daily JHA Form - "+date
newSheet.properties = properties
let query = GTLRSheetsQuery_SpreadsheetsCreate.query(withObject:newSheet)
query.fields = "spreadsheetId"
query.completionBlock = { (ticket, result, NSError) in
if let error = NSError {
print("error!!!!!!!!!!!!!!!!!!!!!!!!!!")
print(error)
}
else {
let response = result as! GTLRSheets_Spreadsheet
let identifier = response.spreadsheetId
self.spreadsheetId = identifier!
GlobalVariable1.sheetID = self.spreadsheetId
print(self.spreadsheetId)
}
}
service.executeQuery(query, completionHandler: nil)
}
//=========================================================================================================
func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
if let error = error {
if (error as NSError).code == GIDSignInErrorCode.hasNoAuthInKeychain.rawValue {
print("The user has not signed in before or they have since signed out.")
} else {
print("(error.localizedDescription)")
}
return
}
// Perform any operations on signed in user here.
let userId = user.userID // For client-side use only!
let idToken = user.authentication.idToken // Safe to send to the server
let fullName = user.profile.name
let givenName = user.profile.givenName
let familyName = user.profile.familyName
let email = user.profile.email
// ...
print(fullName)
}
//=========================================================================================================
// Display (in the UITextView) the names and majors of students in a sample
// spreadsheet:
//https://docs.google.com/spreadsheets/d/e/2PACX-1vTxKKu-0BwyOPq9HTYH237jGlMrf3q8kLwe5R2eH2dbkGqNbk3D7L9_MKxpO4b3g9cy09w2davohJzq/pubhtml
func listMajors() {
let range = "A1:Q"
let query = GTLRSheetsQuery_SpreadsheetsValuesGet
.query(withSpreadsheetId: spreadsheetId, range:range)
service.executeQuery(query) { (ticket, result, error) in
if let error = error {
self.showAlert(title: "Error", message: error.localizedDescription)
return
}
guard let result = result as? GTLRSheets_ValueRange else {
return
}
let rows = result.values!
if rows.isEmpty {
// self.output.text = "No data found."
return
}
// self.output.text = "Number of rows in sheet: (rows.count)"
}
}
// Process the response and display output
func displayResultWithTicket(ticket: GTLRServiceTicket,
finishedWithObject result : GTLRSheets_ValueRange,
error : NSError?) {
if let error = error {
showAlert(title: "Error", message: error.localizedDescription)
return
}
var majorsString = ""
let rows = result.values!
if rows.isEmpty {
// output.text = "No data found."
return
}
majorsString += "Name, Major:n"
for row in rows {
let name = row[0]
let major = row[4]
majorsString += "(name), (major)n"
}
// output.text = majorsString
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertController(
title: title,
message: message,
preferredStyle: UIAlertController.Style.alert
)
let ok = UIAlertAction(
title: "OK",
style: UIAlertAction.Style.default,
handler: nil
)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
struct GlobalVariable1
{
static var sheetID = ""
}
extension UIViewController {
func hideKeyboardWhenTappedAround() {
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(UIViewController.dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
@objc func dismissKeyboard() {
view.endEditing(true)
}
}
HOWEVER I get the following error:
Optional(Error Domain=com.google.GTLRErrorObjectDomain Code=401 "Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project." UserInfo={GTLRStructuredError=GTLRErrorObject 0x600001182310: {errors:[1] message:"Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project." code:401 status:"UNAUTHENTICATED"}, NSLocalizedDescription=Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.})"
What am I doing wrong?
2
Answers
It appears that I was missing an instruction in my functions being called. I also had to update a bit of my code for newer pod file "pod 'GoogleSignIn', '~> 5.0.2'" The instructions for this migration can be found here:
https://developers.google.com/identity/sign-in/ios/quick-migration-guide
The portion of code I needed to add to my functions is a simple line which can be seen here:
service.authorizer = GIDSignIn.sharedInstance().currentUser.authentication.fetcherAuthorizer()
I hope this helps someone else out (pass it on.)
Thank you.
Looking to the error generated by your code, and the scopes you try to require, it seems they are not enough. Indeed you set only:
According to this google official doc about scopes you can find more details about all the scopes.
In the middle part of your code you read profile informations and these info could be readed adding scopes about google sign in: