Swift: Exception when requesting access to Facebook - ios

After updating to XCode 8, Swift 3, and iOS 10, I've begun to get the following error when requesting permission to access Facebook accounts:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Access options are not
permitted for this account type. The options argument must be nil.'
I've found three mentions of this error on SO:
Access options are not permitted for this account type. The options argument must be nil.;
Facebook SDK login crash
NSInvalidArgumentException thrown from ACAccountStore when calling [FBSession openActiveSessionWithPermissions...] on iOS 6.0 and iOS 6.0.1
The first two have no answers; the last suggests checking that the value returned from accountType(withAccountTypeIdentifier ...) isn't nil, which I was already doing.
As mentioned elsewhere, this error is inconsistent with the documentation, which states that Facebook requires an options dictionary.
Here is a small example which reproduces the problem and makes a big show of error checking everything I could think to error check:
import UIKit
import Social
import Accounts
class DVSocialTestViewController: UIViewController {
let localURL = URL(string: "fb://")
let serviceType = SLServiceTypeFacebook
let accountID = ACAccountTypeIdentifierFacebook
var accountType: ACAccountType? {
return ACAccountStore().accountType(withAccountTypeIdentifier: self.accountID)
}
var accounts: [Any]? {
guard let ac = self.accountType else { return nil }
return ACAccountStore().accounts(with: ac)
}
static let appID = "MyAppID"
let basicOptions =
[ACFacebookAppIdKey: DVSocialTestViewController.appID,
ACFacebookPermissionsKey: ["basic_info"],
ACFacebookAudienceKey: ACFacebookAudienceFriends] as [AnyHashable: Any]
let publishOptions =
[ACFacebookAppIdKey:DVSocialTestViewController.appID,
ACFacebookPermissionsKey:["publish_actions"],
ACFacebookAudienceKey:ACFacebookAudienceFriends] as [AnyHashable: Any]
var applicationIsInstalled: Bool {
guard let url = self.localURL else {
return false
}
return UIApplication.shared.canOpenURL(url)
}
var serviceIsAvailable: Bool {
return SLComposeViewController.isAvailable(forServiceType: self.serviceType)
}
var userIsLoggedIn: Bool {
guard let ac = self.accounts, ac.count > 0 else {
NSLog("\(self.accounts)")
return false
}
return true
}
func requestPermissionsForAccountType(_ type: ACAccountType) {
ACAccountStore().requestAccessToAccounts(with: type, options: self.basicOptions) {
(success: Bool, error: Error?) -> Void in
if !success {
NSLog("\(error)")
return
}
ACAccountStore().requestAccessToAccounts(with: type, options: self.publishOptions) {
(success: Bool, error: Error?) -> Void in
if !success {
NSLog("\(error)")
return
}
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard self.applicationIsInstalled else {
NSLog("FB Application is not installed.")
return
}
guard self.serviceIsAvailable else {
NSLog("FB Service is unavailable.")
return
}
guard self.userIsLoggedIn else {
NSLog("User is not logged into FB.")
return
}
guard let AT = self.accountType else {
NSLog("No accounts.")
return
}
self.requestPermissionsForAccountType(AT)
}
}
Has anyone run into this? Any suggestions about what I'm missing?
Edit:
There are few reasons why I feel my question is different from the proposed duplicate.
I'm not using the Facebook SDK.
This is not a "rare case"--it consistently happens with this example every time I run it.
This specifically began happening upon updating to XCode 8, Swift 3, and iOS 10 from the preceding versions.
As stated above, the only answer to the other question (which I admit is not accepted) does not solve my problem.
I've provided a complete, minimal example, which the other question does not.

Related

Can't get weight samples using anchored query

I'm working on a watchOS App as my first Swift/iOS project ever. I want to fetch the latest body weight sample and use it for some calculation. The result is presented to the user. As soon as a new sample is added, I want to update my UI as well. It works in a completely fresh simulator installation. As soon as I add a sample in the iOS simulator, the app updates its UI in the watchOS simulator. However, it doesn't work on my real device or after resetting the watchOS simulator. And I just don't know why. The HKAnchoredObjectQuery just returns 0 samples but I definitely have some samples stored in health. I can even see them under Settings > Health on my watch. I can't imagine this is related to my code, but here it is:
class WeightProvider: ObservableObject {
private static let weightSampleType = HKSampleType.quantityType(forIdentifier: .bodyMass)!
private static let healthStore: HKHealthStore = .init()
private var previousAnchor: HKQueryAnchor?
private var runningQuery: HKAnchoredObjectQuery?
#Published var bodyWeight: Measurement<UnitMass>?
func getBodyWeight(longRunning: Bool = false) {
let query = HKAnchoredObjectQuery(type: Self.weightSampleType, predicate: nil, anchor: previousAnchor, limit: longRunning ? HKObjectQueryNoLimit : 1, resultsHandler: processQueryResult)
if longRunning {
query.updateHandler = processQueryResult
runningQuery = query
}
Self.healthStore.execute(query)
}
func stopLongRunningQuery() {
if let runningQuery = runningQuery {
Self.healthStore.stop(runningQuery)
self.runningQuery = nil
}
}
private func processQueryResult(_: HKAnchoredObjectQuery, samples: [HKSample]?, _: [HKDeletedObject]?, newAnchor: HKQueryAnchor?, error: Error?) {
guard let samples = samples as? [HKQuantitySample], error == nil else {
fatalError(error?.localizedDescription ?? "Failed to cast [HKSample] to [HKQuantitySample]")
}
previousAnchor = newAnchor
guard let sample = samples.last else {
return
}
DispatchQueue.main.async {
if Locale.current.usesMetricSystem {
let weight = sample.quantity.doubleValue(for: .gramUnit(with: .kilo))
self.bodyWeight = .init(value: weight, unit: UnitMass.kilograms)
} else {
let weight = sample.quantity.doubleValue(for: .pound())
self.bodyWeight = .init(value: weight, unit: UnitMass.pounds)
}
}
}
}
// MARK: - HealthKit Authorization
extension WeightProvider {
private static let typesToRead: Set<HKObjectType> = [
weightSampleType,
]
func authorize(completion: #escaping (Bool, Error?) -> Swift.Void) {
Self.healthStore.requestAuthorization(toShare: nil, read: Self.typesToRead) { success, error in
completion(success, error)
}
}
}
In my Views onAppear I call this function:
private func authorizeHealthKit() {
guard firstRun else {
return
}
firstRun = false
weightProvider.authorize { success, error in
guard success, error == nil else {
return
}
weightProvider.getBodyWeight(longRunning: true)
}
}
HealthKit is properly authorized as I can see in the Settings of my Watch. Any ideas? Any tips for my code in general?
Wow, after all this time I found the issue: The line previousAnchor = newAnchor needs to be after the guard statement. That's it.

What is the "uncaught exception"? [closed]

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 5 years ago.
Improve this question
I'm trying to build a simple iPhone app that saves user entered text to iCloud, using a UITextView and a UIButton. I've never done something like this, so I'm following this tutorial. So far their tutorials have worked flawlessly for me, until this one. The app builds fine, and runs on my iPhone, displaying a place to enter text, and a button to save the text to iCloud. But when I tap the screen to enter text, the app crashes with this message:
2017-03-24 08:45:56.093572 Gaethr2[6342:1419021] * Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '* -[NSMetadataQuery valueOfAttribute:forResultAtIndex:]: index (0) out of bounds (0)'
*** First throw call stack:
(0x184b611b8 0x18359855c 0x184b61100 0x1855f1460 0x100056ffc 0x10005857c 0x184afab10 0x184afa214 0x184af9f90 0x184b69b8c 0x184a3be64 0x185570e0c 0x1855ef268 0x184afab10 0x184afa214 0x184af9f90 0x184b69b8c 0x184a3be64 0x185570e0c 0x1968d93e4 0x1968db29c 0x184b0ea44 0x184b0e240 0x184b0c094 0x184a3a2b8 0x1864ee198 0x18aa817fc 0x18aa7c534 0x10005b720 0x183a1d5b8)
libc++abi.dylib: terminating with uncaught exception of type NSException
Message from debugger: failed to send the k packet
Here's the ViewController code:
import UIKit
class ViewController: UIViewController
{
#IBOutlet weak var textView: UITextView!
var document: MyDocument?
var documentURL: URL?
var ubiquityURL: URL?
var metaDataQuery: NSMetadataQuery?
func metadataQueryDidFinishGathering(notification: NSNotification) -> Void
{
let query: NSMetadataQuery = notification.object as! NSMetadataQuery
query.disableUpdates()
NotificationCenter.default.removeObserver(self,
name: NSNotification.Name.NSMetadataQueryDidFinishGathering,
object: query)
query.stop()
let resultURL = query.value(ofAttribute: NSMetadataItemURLKey,
forResultAt: 0) as! URL
if query.resultCount == 1 {
let resultURL = query.value(ofAttribute: NSMetadataItemURLKey,
forResultAt: 0) as! URL
document = MyDocument(fileURL: resultURL as URL)
document?.open(completionHandler: {(success: Bool) -> Void in
if success {
print("iCloud file open OK")
self.textView.text = self.document?.userText
self.ubiquityURL = resultURL as URL
} else {
print("iCloud file open failed")
}
})
} else {
document = MyDocument(fileURL: ubiquityURL!)
document?.save(to: ubiquityURL!,
for: .forCreating,
completionHandler: {(success: Bool) -> Void in
if success {
print("iCloud create OK")
} else {
print("iCloud create failed")
}
})
}
}
override func viewDidLoad()
{
super.viewDidLoad()
let filemgr = FileManager.default
ubiquityURL = filemgr.url(forUbiquityContainerIdentifier: nil)
guard ubiquityURL != nil else {
print("Unable to access iCloud Account")
print("Open the Settings app and enter your Apple ID into iCloud settings")
return
}
ubiquityURL = ubiquityURL?.appendingPathComponent(
"Documents/savefile.txt")
metaDataQuery = NSMetadataQuery()
metaDataQuery?.predicate =
NSPredicate(format: "%K like 'savefile.txt'",
NSMetadataItemFSNameKey)
metaDataQuery?.searchScopes =
[NSMetadataQueryUbiquitousDocumentsScope]
NotificationCenter.default.addObserver(self,
selector: #selector(
ViewController.metadataQueryDidFinishGathering),
name: NSNotification.Name.NSMetadataQueryDidFinishGathering,
object: metaDataQuery!)
metaDataQuery!.start()
}
#IBAction func saveDocument(_ sender: AnyObject)
{
document!.userText = textView.text
document?.save(to: ubiquityURL!,
for: .forOverwriting,
completionHandler: {(success: Bool) -> Void in
if success {
print("Save overwrite OK")
} else {
print("Save overwrite failed")
}
})
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Here's the code for MyDocument.Swift
import UIKit
class MyDocument: UIDocument
{
var userText: String? = "Some Sample Text"
override func contents(forType typeName: String) throws -> Any
{
if let content = userText
{
let length =
content.lengthOfBytes(using: String.Encoding.utf8)
return NSData(bytes:content, length: length)
}
else
{
return Data()
}
}
override func load(fromContents contents: Any, ofType typeName: String?) throws
{
if let userContent = contents as? Data
{
userText = NSString(bytes: (contents as AnyObject).bytes,
length: userContent.count,
encoding: String.Encoding.utf8.rawValue) as? String
}
}
}
Here's a screenshot of the crash:
Thank you for your help!
The uncaught exception is
-[NSMetadataQuery valueOfAttribute:forResultAtIndex:]: index (0) out of bounds (0)'
Delete the first occurrence of
let resultURL = query.value(ofAttribute: NSMetadataItemURLKey,
forResultAt: 0) as! URL
It's redundant and causes the error if query.results is empty

Updating object in Parse iOS, [Error]: Object not found

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
// Set up the Parse SDK
let configuration = ParseClientConfiguration {
$0.applicationId = "WhatsTheHW"
$0.server = "https://whatsthehw-parse-alan.herokuapp.com/parse"
}
Parse.initializeWithConfiguration(configuration)
let query = PFQuery(className: "Course")
query.findObjectsInBackgroundWithBlock {(result: [PFObject]?, error: NSError?) -> Void in
for object in result! {
// existing objectIds: 1Ja2Hx77zA, 34AF1vKO6f, 5FWlsswxw0
if object.objectId == "34AF1vKO6f" {
object["studentRelation"] = ["hi", "ih"]
object.saveInBackgroundWithBlock{(success, error) in
if success == true {
print("\(object) saved to parse")
} else {
print("save failed: \(error)")
}
}
}
}
}
return true
}
This is the minimum I can reduce this task to (this code is at AppDelegate).
It all worked fine when I tried using REST api and api console in parse dashboard but it doesn't work with iOS sdk.
The error I'm getting from the print statement is
Error Domain=Parse Code=101 "Object not found." UserInfo={code=101, temporary=0, error=Object not found., NSLocalizedDescription=Object not found.}
It works if I'm simply adding a new object like this :
let object = PFObject(className: "Course")
object["name"] = "German"
object["studentRelation"] = ["a", "b"]
object.saveInBackgroundWithBlock{(success, error) in
if success == true {
print("save completed")
print("\(object) saved to parse")
} else {
print("save failed: \(error)")
}
}
I'm really lost and I don't know why this is happening.
Thanks in advance.
This issue might relate to the access rights (ACL) of the object that you're trying to save. [Error]: Object not found is printed when a user that doesn't have write access to an object tries to save it, the error message of the Parse SDK is really misleading here!
Make sure the parse user who is trying to save the object has the proper writes to actually write on that object in your parse DB.
An easy fix will be to set the default ACL inside your app to public read + write:
let acl = PFACL()
acl.publicReadAccess = true
acl.publicWriteAccess = true
PFACL.setDefaultACL(acl, withAccessForCurrentUser: true)
Be careful with this approach though, usually you want to set access rights according to the actual role of the user. So a better alternative would be to only set the ACL on the PFObject when you're creating it and only give write access to the users you know should be able to alter the object.
can you please first find your object, save into another object and run the saveInBackground outside of the loop.
Your code should look like the following:
var objectToSave : PFObject?
let query = PFQuery(className: "Course")
query.findObjectsInBackgroundWithBlock {(result: [PFObject]?, error: NSError?) -> Void in
for object in result! {
if object.objectId == "jMIxdSXNRH" {
objectToSave = object
}
}
if objectToSave != nil {
objectToSave!["studentRelation"] = ["hi", "ih"]
objectToSave!.saveInBackgroundWithBlock{(success, error) in
if success == true {
print("\(objectToSave) saved to parse")
} else {
print("save failed: \(error)")
}
}
}
}
I am using my objectId's and not your so please change them to yours :)

Apps Script API errors on given ViewController

Has anyone gone through this API and has figured it out?
This is my third time trying to get this to work by following this guide
I am using the swift version of this guide.
Google Apps Script Guide
And it always give me the same errors.
import UIKit
class ViewController: UIViewController {
private let kKeychainItemName = "Google Apps Script Execution API"
private let kClientID = "493692471278-3mf6bo212flgjopl06hrjfeepphe70h4.apps.googleusercontent.com"
private let kScriptId = "Mj0RNm2ZtohFurieBLPwnxYAb4Jnnku4P"
// If modifying these scopes, delete your previously saved credentials by
// resetting the iOS simulator or uninstall the app.
private let scopes = ["https://www.googleapis.com/auth/drive"]
private let service = GTLService() // error Use of unresolved identifier 'GTLService'
let output = UITextView()
// When the view loads, create necessary subviews
// and initialize the Google Apps Script Execution API service
override func viewDidLoad() {
super.viewDidLoad()
output.frame = view.bounds
output.editable = false
output.contentInset = UIEdgeInsets(top: 20, left: 0, bottom: 20, right: 0)
output.autoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth
// error*** Binary operator '|' cannot be applied to two 'UIViewAutoresizing' operands
view.addSubview(output);
// Error**Use of unresolved identifier 'GTMOAuth2ViewControllerTouch'
if let auth = GTMOAuth2ViewControllerTouch.authForGoogleFromKeychainForName(
kKeychainItemName,
clientID: kClientID,
clientSecret: nil) {
service.authorizer = auth
}
}
// When the view appears, ensure that the Google Apps Script Execution API service is authorized
// and perform API calls
override func viewDidAppear(animated: Bool) {
if let authorizer = service.authorizer,
canAuth = authorizer.canAuthorize where canAuth {
callAppsScript()
} else {
presentViewController(
createAuthController(),
animated: true,
completion: nil
)
}
}
// Calls an Apps Script function to list the folders in the user's
// root Drive folder.
func callAppsScript() {
output.text = "Getting folders..."
let baseUrl = "https://script.googleapis.com/v1/scripts/\(kScriptId):run"
let url = GTLUtilities.URLWithString(baseUrl, queryParameters: nil)
// error ** Use of unresolved identifier 'GTLUtilities'
// Create an execution request object.
var request = GTLObject()
// Error** Use of unresolved identifier 'GTLObject'
request.setJSONValue("getFoldersUnderRoot", forKey: "function")
// Make the API request.
service.fetchObjectByInsertingObject(request,
forURL: url,
delegate: self,
didFinishSelector: "displayResultWithTicket:finishedWithObject:error:")
}
// Displays the retrieved folders returned by the Apps Script function.
func displayResultWithTicket(ticket: GTLServiceTicket,
finishedWithObject object : GTLObject,
error : NSError?) {
if let error = error {
// The API encountered a problem before the script
// started executing.
showAlert("The API returned the error: ",
message: error.localizedDescription)
return
}
if let apiError = object.JSON["error"] as? [String: AnyObject] {
// The API executed, but the script returned an error.
// Extract the first (and only) set of error details and cast as
// a Dictionary. The values of this Dictionary are the script's
// 'errorMessage' and 'errorType', and an array of stack trace
// elements (which also need to be cast as Dictionaries).
let details = apiError["details"] as! [[String: AnyObject]]
var errMessage = String(
format:"Script error message: %#\n",
details[0]["errorMessage"] as! String)
if let stacktrace =
details[0]["scriptStackTraceElements"] as? [[String: AnyObject]] {
// There may not be a stacktrace if the script didn't start
// executing.
for trace in stacktrace {
let f = trace["function"] as? String ?? "Unknown"
let num = trace["lineNumber"] as? Int ?? -1
errMessage += "\t\(f): \(num)\n"
}
}
// Set the output as the compiled error message.
output.text = errMessage
} else {
// The result provided by the API needs to be cast into the
// correct type, based upon what types the Apps Script function
// returns. Here, the function returns an Apps Script Object with
// String keys and values, so must be cast into a Dictionary
// (folderSet).
let response = object.JSON["response"] as! [String: AnyObject]
let folderSet = response["result"] as! [String: AnyObject]
if folderSet.count == 0 {
output.text = "No folders returned!\n"
} else {
var folderString = "Folders under your root folder:\n"
for (id, folder) in folderSet {
folderString += "\t\(folder) (\(id))\n"
}
output.text = folderString
}
}
}
// Creates the auth controller for authorizing access to Google Apps Script Execution API
private func createAuthController() -> GTMOAuth2ViewControllerTouch {
// Error** Use of undeclared type 'GTLServiceTicket' let scopeString = " ".join(scopes) // Error* 'join' is unavailable: call the 'joinWithSeparator()' method on the sequence of elements
return GTMOAuth2ViewControllerTouch(
scope: scopeString,
clientID: kClientID,
clientSecret: nil,
keychainItemName: kKeychainItemName,
delegate: self,
finishedSelector: "viewController:finishedWithAuth:error:"
)
}
// Handle completion of the authorization process, and update the Google Apps Script Execution API
// with the new credentials.
func viewController(vc : UIViewController,
finishedWithAuth authResult : GTMOAuth2Authentication, error : NSError?)
// Error** Use of undeclared type 'GTMOAuth2Authentication' {
if let error = error {
service.authorizer = nil
showAlert("Authentication Error", message: error.localizedDescription)
return
}
service.authorizer = authResult
dismissViewControllerAnimated(true, completion: nil)
}
// Helper for showing an alert
func showAlert(title : String, message: String) {
let alert = UIAlertView(
title: title,
message: message,
delegate: nil,
cancelButtonTitle: "OK"
)
alert.show()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I find it hard to believe that google would make a guide and have it not work for the current version of xcode. It even says at the bottom of their guide that it was last updated February 2016.
Wanted to see if anyone has had any luck with following this guide in the past.
is there another swift guide for this google API?
Thank you in advance.
1)Looks like you aren't getting the header files. You need a Bridging-Header.h. That step is omitted from the Quickstart; see the Google Drive Quickstart for the step you need to take.
2)You can just comment out the following line, it doesn't seem to be necessary.
output.autoresizingMask = UIViewAutoresizing.FlexibleHeight |
UIViewAutoresizing.FlexibleWidth
3)Those changes will fix the compile problems. I found that after fixing the compile problems, it would not link and run on a phone.
The Quickstart guide has you link the GTL.framework with your app using Xcode's 'Link Binary with Libraries'. I instead installed it using Cocoapods (https://cocoapods.org/pods/Google-API-Client) and was then able to run my app.

Unique identifiers on iOS Phonebook are not as expected

I am using the new version of retrieving contacts from the Phonebook on iOS. There is a problem with the unique identifier, which is returned by the first query. Normally the unique identifier is a UUID, but sometimes it is appended by “: ABPerson”.
Querying the Phonebook API with this identifier works sometimes, but not always. Does anybody know, if there is a way to avoid this behaviour?
Currently, I try with a safeguard of two queries. The first is using the identifier as is, the second (if the first fails) is to use the identifier stripping the “: ABPerson” extension.
The first query used is:
class func getAllPhoneBookContacts() -> [PhonebookContact] {
let contactStore = CNContactStore()
var contacts = [PhonebookContact]()
PhoneBookContactsHelper.requestForAccess { (accessGranted) -> Void in
if accessGranted {
let keys = [CNContactIdentifierKey, CNContactPhoneNumbersKey]
do {
let fetchRequest = CNContactFetchRequest(keysToFetch: keys)
try contactStore.enumerateContactsWithFetchRequest(fetchRequest, usingBlock: { (contact: CNContact, _) -> Void in
for phoneNoLab in contact.phoneNumbers {
if let phoneNo = phoneNoLab.value as? CNPhoneNumber,
normalizedPhoneNumber = PhoneNumberNormalizer.normalizePhoneNumber(phoneNo.stringValue) {
let pbc = PhonebookContact(contactID: contact.identifier, phoneNumber: normalizedPhoneNumber)
contacts.append(pbc)
}
}
})
}
catch {
NSLog("Unable to fetch contacts.")
}
}
}
return contacts
}
Accessing a certain contact again later by the identifier is done by:
class func getContactNameByUUID(identifier: String) -> String?{
var name : String?
PhoneBookContactsHelper.requestForAccess()
{ (accessGranted) -> Void in
if accessGranted {
let contactStore = CNContactStore()
let keys = [CNContactFormatter.descriptorForRequiredKeysForStyle(.FullName)]
do {
let cnc = try contactStore.unifiedContactWithIdentifier(identifier, keysToFetch: keys)
name = CNContactFormatter.stringFromContact(cnc, style: .FullName)!
}
catch _ {
NSLog("Could not fetch contact with id \(identifier))")
}
}
}
return name
}
I am using iOS 9 and tested on simulator and various iPhones and the unexpected behaviour is present everywhere.

Resources