I am using the following code to attempt to create a shared private record:
#IBAction func testPress(_ sender: Any) {
let customZone = CKRecordZone(zoneName: "ShareZone")
let friendRecord = CKRecord(recordType: "Share", zoneID: customZone.zoneID)
let rootRecord = CKRecord(recordType: "Root", zoneID: customZone.zoneID)
model.privateDB.delete(withRecordZoneID: customZone.zoneID) { (nil, error) in
self.model.privateDB.save(customZone) { (nil, error) in
print("Custom Zone Error = \(error)")
self.model.privateDB.save(friendRecord, completionHandler: { (nil, error) in
self.model.privateDB.save(rootRecord, completionHandler: { (nil, error) in
self.shareTest(record: friendRecord, root: rootRecord)
})
})
}
}
}
func shareTest(record:CKRecord, root:CKRecord) {
record["Name"] = "Test" as CKRecordValue?
root["Name"] = "Test" as CKRecordValue?
let ckContainer = CKContainer.default()
let shareRecord = CKShare(rootRecord: root, share: record.recordID)
shareRecord[CKShareTitleKey] = "Name" as CKRecordValue?
let shareController = UICloudSharingController(share: shareRecord, container: ckContainer)
shareController.delegate = self
shareController.availablePermissions = [.allowReadOnly]
self.present(shareController, animated: false)
}
However I am returning the error when I press on a way to share the link:
CKError 0x6000002535f0: "Invalid Arguments" (12); "An added share is being saved without its rootRecord (CKRecordID: 0x608000224560; recordName=C0ADC819-57F7-4D99-A527-B21590F506AB, zoneID=ShareZone:defaultOwner)"
I looked at this answer Link who was having the same problem, but do not quite know how to get their solution to work as they didn't provide enough details.
Does anyone know what I am doing wrong?
I believe the error message is telling you need to save the share and root record at the same time.
let modifyRecordsOperation = CKModifyRecordsOperation( recordsToSave: [record, share], recordIDsToDelete: nil)
You should do this operation in the completion handler of the sharingController.
sharingController = UICloudSharingController {
controller, preparationCompletionHandler in
Edit: Your code would look something like this not tested code:
#IBAction func testPress(_ sender: Any) {
let privatedatabase = CKContainer.default().privateCloudDatabase
let newZoneName = UUID().uuidString
let recordZone = CKRecordZone(zoneName: "ShareZone")
privatedatabase.save(recordZone) { savedRecordZone, error in
if let error = error {
print("\(error.localizedDescription).")
} else if let savedRecordZone = savedRecordZone {
print("\(error.localizedDescription).")
let rootRecord = CKRecord(recordType: "RootRecord", zoneID: savedRecordZone.zoneID)
rootRecord["Name"] = "Test" as CKRecordValue?
privatedatabase.save(rootRecord) { record, error in
if let error = error {
print("\(error.localizedDescription).")
} else if let record = record {
print("successfully added rootRecord to cloud.")
self.shareTest( rootRecord: record)
}
}
}
}
}
func shareTest(rootRecord:CKRecord) {
let ckContainer = CKContainer.default()
let shareRecord = CKShare(rootRecord: rootRecord)
let sharingController = UICloudSharingController { controller, preparationCompletionHandler in
let share = CKShare(rootRecord: record)
share[CKShareTitleKey] = "Share Title" as CKRecordValue
share.publicPermission = .none
let modifyRecordsOperation = CKModifyRecordsOperation( recordsToSave: [rootRecord, share], recordIDsToDelete: nil)
modifyRecordsOperation.timeoutIntervalForRequest = 10
modifyRecordsOperation.timeoutIntervalForResource = 10
modifyRecordsOperation.modifyRecordsCompletionBlock = { records, recordIDs, error in
if let error = error {
print(error.localizedDescription)
}
if let records = records {
print("Share and Root records saved successfully")
}
preparationCompletionHandler(share, CKContainer(identifier: ckContainerID ), error)
}
myCloudDatabase.add(modifyRecordsOperation)
}
if let sharingController = sharingController {
sharingController.availablePermissions = [.allowReadOnly, .allowReadWrite, .allowPrivate]
sharingController.popoverPresentationController?.sourceView = sender
sharingController.delegate = self
self.present(sharingController, animated: true)
}
}
// MARK: UICloudSharingControllerDelegate
// --------------------------------------
func cloudSharingController(_ csc: UICloudSharingController, failedToSaveShareWithError error: Error) {
print("Failed to share to cloud: \(error)")
}
func itemTitle(for csc: UICloudSharingController) -> String? {
return "Please join rootRecord share."
}
func cloudSharingControllerDidStopSharing(_ csc: UICloudSharingController) {
print("Cloudkit stopped sharing")
}
func cloudSharingControllerDidSaveShare(_ csc: UICloudSharingController) {
print("Cloudkit started sharing rootRecord")
}
Related
I've logged in with firebase in my iOS app and I have the Google Sheets API pod initialized in the podfile but I'm getting this error when I try to append data to my spreadsheet:
Request is missing required authentication credential. Expected OAuth 2 access token, login cookie or other valid authentication credential.
Here is my spreadsheets code:
//
// SpredsheetsController.swift
// frcscout
//
// Created by Elliot Scher on 12/21/22
//
import UIKit
import GoogleAPIClientForREST
import GoogleSignIn
class SpreadsheetsController: UIViewController {
let utils = Utils()
let sheetService = GTLRSheetsService()
override func viewDidLoad() {
super.viewDidLoad()
sheetService.authorizer = GIDSignIn.sharedInstance.currentUser?.authentication.fetcherAuthorizer()
}
#IBAction func appendDataPressed(_ sender: UIButton) {
appendData()
}
#IBAction func specificCellPressed(_ sender: UIButton) {
sendDataToCell()
}
#IBAction func readDataPressed(_ sender: UIButton) {
readData()
}
#IBAction func readSheetsPressed(_ sender: UIButton) {
readSheets()
}
}
extension SpreadsheetsController {
func appendData() {
let spreadsheetId = K.sheetID
let range = "A1:Q"
let rangeToAppend = GTLRSheets_ValueRange.init();
let data = ["this", "is", "a", "test"]
rangeToAppend.values = [data]
let query = GTLRSheetsQuery_SpreadsheetsValuesAppend.query(withObject: rangeToAppend, spreadsheetId: spreadsheetId, range: range)
query.valueInputOption = "USER_ENTERED"
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print("Error in appending data: \(error)")
} else {
print("Data sent: \(data)")
}
}
}
func sendDataToCell() {
let spreadsheetId = K.sheetID
let currentRange = "A5:B5" //Any range on the sheet, for instance: A5:B6
let results = ["this is a test"]
let rangeToAppend = GTLRSheets_ValueRange.init();
rangeToAppend.values = [results]
let query = GTLRSheetsQuery_SpreadsheetsValuesUpdate.query(withObject: rangeToAppend, spreadsheetId: spreadsheetId, range: currentRange)
query.valueInputOption = "USER_ENTERED"
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print(error)
} else {
print("Sending: \(results)")
}
}
}
func readData() {
print("Getting sheet data...")
let spreadsheetId = K.sheetID
let range = "A1:Q"
let query = GTLRSheetsQuery_SpreadsheetsValuesGet
.query(withSpreadsheetId: spreadsheetId, range:range)
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print(error)
return
}
guard let result = result as? GTLRSheets_ValueRange else {
return
}
let rows = result.values!
var stringRows = rows as! [[String]]
for row in stringRows {
stringRows.append(row)
print(row)
}
if rows.isEmpty {
print("No data found.")
return
}
print("Number of rows in sheet: \(rows.count)")
}
}
func readSheets() {
print("func findSpreadNameAndSheets executing...")
let spreadsheetId = K.sheetID
let query = GTLRSheetsQuery_SpreadsheetsGet.query(withSpreadsheetId: spreadsheetId)
sheetService.executeQuery(query) { (ticket, result, error) in
if let error = error {
print(error)
} else {
let result = result as? GTLRSheets_Spreadsheet
let sheets = result?.sheets
if let sheetInfo = sheets {
for info in sheetInfo {
print("New sheet found: \(String(describing: info.properties?.title))")
}
}
}
}
}
}
Here is my authentication code:
//
// AuthenticationViewModel.swift
// frcscout
//
// Created by Elliot Scher on 12/15/22.
//
import Foundation
import Firebase
import GoogleSignIn
class AuthenticationViewModel: ObservableObject {
var credential:AuthCredential? = nil
enum SignInState {
case signedIn
case signedOut
}
#Published var state: SignInState = .signedOut
func signIn() {
// 1
if GIDSignIn.sharedInstance.hasPreviousSignIn() {
GIDSignIn.sharedInstance.restorePreviousSignIn { [unowned self] user, error in
authenticateUser(for: user, with: error)
}
} else {
guard let clientID = FirebaseApp.app()?.options.clientID else { return }
let configuration = GIDConfiguration(clientID: clientID)
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return }
guard let rootViewController = windowScene.windows.first?.rootViewController else { return }
GIDSignIn.sharedInstance.signIn(with: configuration, presenting: rootViewController) { [unowned self] user, error in
authenticateUser(for: user, with: error)
}
}
}
private func authenticateUser(for user: GIDGoogleUser?, with error: Error?) {
if let error = error {
print(error.localizedDescription)
return
}
guard let authentication = user?.authentication, let idToken = authentication.idToken else { return }
credential = GoogleAuthProvider.credential(withIDToken: idToken, accessToken: authentication.accessToken)
Auth.auth().signIn(with: credential!) { [unowned self] (_, error) in
if let error = error {
print(error.localizedDescription)
} else {
self.state = .signedIn
}
}
}
func signOut() {
GIDSignIn.sharedInstance.signOut()
do {
try Auth.auth().signOut()
state = .signedOut
} catch {
print(error.localizedDescription)
}
}
func getCredential() -> AuthCredential {
return credential!
}
}
I think I need an oauth2 token but I'm not sure how to get it from firebase. Could someone help me resolve this issue? Thanks!!
Resolved:
I had to put this line:
sheetService.authorizer = GIDSignIn.sharedInstance.currentUser?.authentication.fetcherAuthorizer()
at the beginning of each method.
I'm doing my very first IOS app using Cloud Firestore and have to make the same queries to my database repeatedly. I would like to get rid of the duplicate lines of code. This is examples of func where documents ID are duplicated. Also I using other queries as .delete(), .addSnapshotListener(), .setData(). Should I refactor all that queries somehow or leave them because they were used just for one time?
#objc func updateUI() {
inputTranslate.text = ""
inputTranslate.backgroundColor = UIColor.clear
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { [self] (document, error) in
if let document = document, document.exists {
let document = document
let label = document.data()?.keys.randomElement()!
self.someNewWord.text = label
// Fit the label into screen
self.someNewWord.adjustsFontSizeToFitWidth = true
self.checkButton.isHidden = false
self.inputTranslate.isHidden = false
self.deleteBtn.isHidden = false
} else {
self.checkButton.isHidden = true
self.inputTranslate.isHidden = true
self.deleteBtn.isHidden = true
self.someNewWord.adjustsFontSizeToFitWidth = true
self.someNewWord.text = "Add your first word to translate"
updateUI()
}
}
}
#IBAction func checkButton(_ sender: UIButton) {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { (document, error) in
let document = document
let label = self.someNewWord.text!
let currentTranslate = document!.get(label) as? String
let translateField = self.inputTranslate.text!.lowercased().trimmingCharacters(in: .whitespaces)
if translateField == currentTranslate {
self.inputTranslate.backgroundColor = UIColor.green
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
self.inputTranslate.backgroundColor = UIColor.clear
updateUI()}
} else {
self.inputTranslate.backgroundColor = UIColor.red
self.inputTranslate.shakingAndRedBg()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [self] in
self.inputTranslate.backgroundColor = UIColor.clear
self.inputTranslate.text = ""
}
}
}
}
func deletCurrentWord () {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.getDocument { (document, err) in
let document = document
if let err = err {
print("Error getting documents: \(err)")
} else {
let array = document!.data()
let counter = array!.count
if counter == 1 {
// The whole document will deleted together with a last word in list.
let user = Auth.auth().currentUser?.email
self.db.collection(K.FStore.collectionName).document(user!).delete() { err in
if let err = err {
print("Error removing document: \(err)")
} else {
self.updateUI()
}
}
} else {
// A current word will be deleted
let user = Auth.auth().currentUser?.email
let wordForDelete = self.someNewWord.text!
self.db.collection(K.FStore.collectionName).document(user!).updateData([
wordForDelete: FieldValue.delete()
]) { err in
if let err = err {
print("Error updating document: \(err)")
} else {
self.updateUI()
}
}
}
}
}
}
Another query example
func loadMessages() {
let user = Auth.auth().currentUser?.email
let docRef = db.collection(K.FStore.collectionName).document(user!)
docRef.addSnapshotListener { (querySnapshot, error) in
self.messages = []
if let e = error {
print(e)
} else {
if let snapshotDocuments = querySnapshot?.data(){
for item in snapshotDocuments {
if let key = item.key as? String, let translate = item.value as? String {
let newMessage = Message(key: key, value: translate)
self.messages.append(newMessage)
}
}
DispatchQueue.main.async {
self.messages.sort(by: {$0.value > $1.value})
self.secondTableView.reloadData()
let indexPath = IndexPath(row: self.messages.count - 1, section: 0)
self.secondTableView.scrollToRow(at: indexPath, at: .top, animated: false)
}
}
}
}
}
}
enum Error {
case invalidUser
case noDocumentFound
}
func fetchDocument(onError: #escaping (Error) -> (), completion: #escaping (FIRQueryDocument) -> ()) {
guard let user = Auth.auth().currentUser?.email else {
onError(.invalidUser)
return
}
db.collection(K.FStore.collectionName).document(user).getDocument { (document, error) in
if let error = error {
onError(.noDocumentFound)
} else {
completion(document)
}
}
}
func updateUI() {
fetchDocument { [weak self] error in
self?.hideShowViews(shouldHide: true, newWordText: nil)
} completion: { [weak self] document in
guard document.exists else {
self?.hideShowViews(shouldHide: true, newWordText: nil)
return
}
self?.hideShowViews(shouldHide: false, newWordText: document.data()?.keys.randomElement())
}
}
private func hideShowViews(shouldHide: Bool, newWordText: String?) {
checkButton.isHidden = shouldHide
inputTranslate.isHidden = shouldHide
deleteBtn.isHidden = shouldHide
someNewWord.adjustsFontSizeToFitWidth = true
someNewWord.text = newWordText ?? "Add your first word to translate"
}
The updateUI method can easily be refactored using a simple guard statement and then taking out the common code into a separate function. I also used [weak self] so that no memory leaks or retain cycles occur.
Now, you can follow the similar approach for rest of the methods.
Use guard let instead of if let to avoid nesting.
Use [weak self] for async calls to avoid memory leaks.
Take out the common code into a separate method and use a Bool flag to hide/show views.
Update for step 3:
You can create methods similar to async APIs for getDocument() or delete() etc and on completion you can update UI or perform any action. You can also create a separate class and move the fetchDocument() and other similar methods in there and use them.
I am not able to load the documents in chat application in Swift IOS using Firestore database, though able to successfully retrieve the data from the Firestore database, I have added the deinit method as well please assist further to resolve the error, I have added the complete view controller , please help me
Error
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (47) must be equal to the number of rows contained in that section before the update (23), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Code
let kBannerAdUnitID = "ca-app-pub-3940256099942544/2934735716"
#objc(FCViewController)
class FCViewController: UIViewController, UITableViewDataSource, UITableViewDelegate,
UITextFieldDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
// Instance variables
#IBOutlet weak var textField: UITextField!
#IBOutlet weak var sendButton: UIButton!
var ref : CollectionReference!
var ref2: DocumentReference!
var messages: [DocumentSnapshot]! = []
var msglength: NSNumber = 10
fileprivate var _refHandle: CollectionReference!
var storageRef: StorageReference!
var remoteConfig: RemoteConfig!
private let db = Firestore.firestore()
private var reference: CollectionReference?
private let storage = Storage.storage().reference()
// private var messages = [Constants.MessageFields]()
//snapshot private var messages: [Constants.MessageFields] = []
private var messageListener: ListenerRegistration?
// var db:Firestore!
#IBOutlet weak var banner: GADBannerView!
#IBOutlet weak var clientTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.clientTable.register(UITableViewCell.self, forCellReuseIdentifier: "tableViewCell")
// clientTable.delegate = self
//clientTable.dataSource = self
//db = Firestore.firestore()
ref = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages")
ref2 = db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").document()
configureDatabase()
configureStorage()
configureRemoteConfig()
fetchConfig()
loadAd()
}
deinit {
if let refhandle = _refHandle {
let listener = ref.addSnapshotListener { querySnapshot, error in
}
listener.remove()
}
}
func configureDatabase() {
db.collection("messages").document("hello").collection("newmessages").document("2").collection("hellos").document("K").collection("messages").addSnapshotListener { querySnapshot, error in
guard let documents = querySnapshot?.documents else {
print("Error fetching documents: \(error!)")
return
}
/* let name = documents.map { $0["name"]!}
let text = documents.map { $0["text"]!}
let photourl = documents.map { $0["photoUrl"]!}
print(name)
print(text)
print(photourl)*/
self.messages.append(contentsOf: documents)
// self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
//self.clientTable.reloadData()
}
}
func configureStorage() {
storageRef = Storage.storage().reference()
}
func configureRemoteConfig() {
remoteConfig = RemoteConfig.remoteConfig()
let remoteConfigSettings = RemoteConfigSettings(developerModeEnabled: true)
remoteConfig.configSettings = remoteConfigSettings
}
func fetchConfig() {
var expirationDuration: Double = 3600
// If in developer mode cacheExpiration is set to 0 so each fetch will retrieve values from
// the server.
if self.remoteConfig.configSettings.isDeveloperModeEnabled {
expirationDuration = 0
}
remoteConfig.fetch(withExpirationDuration: expirationDuration) { [weak self] (status, error) in
if status == .success {
print("Config fetched!")
guard let strongSelf = self else { return }
strongSelf.remoteConfig.activateFetched()
let friendlyMsgLength = strongSelf.remoteConfig["friendly_msg_length"]
if friendlyMsgLength.source != .static {
strongSelf.msglength = friendlyMsgLength.numberValue!
print("Friendly msg length config: \(strongSelf.msglength)")
}
} else {
print("Config not fetched")
if let error = error {
print("Error \(error)")
}
}
}
}
#IBAction func didPressFreshConfig(_ sender: AnyObject) {
fetchConfig()
}
#IBAction func didSendMessage(_ sender: UIButton) {
_ = textFieldShouldReturn(textField)
}
#IBAction func didPressCrash(_ sender: AnyObject) {
print("Crash button pressed!")
Crashlytics.sharedInstance().crash()
}
func inviteFinished(withInvitations invitationIds: [String], error: Error?) {
if let error = error {
print("Failed: \(error.localizedDescription)")
} else {
print("Invitations sent")
}
}
func loadAd() {
self.banner.adUnitID = kBannerAdUnitID
self.banner.rootViewController = self
self.banner.load(GADRequest())
}
// UITableViewDataSource protocol methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Dequeue cell
let cell = self.clientTable .dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath)
// Unpack message from Firebase DataSnapshot
let messageSnapshot: DocumentSnapshot! = self.messages[indexPath.row]
guard let message = messageSnapshot as? [String:String] else { return cell }
let name = message[Constants.MessageFields.name] ?? ""
if let imageURL = message[Constants.MessageFields.imageURL] {
if imageURL.hasPrefix("gs://") {
Storage.storage().reference(forURL: imageURL).getData(maxSize: INT64_MAX) {(data, error) in
if let error = error {
print("Error downloading: \(error)")
return
}
DispatchQueue.main.async {
cell.imageView?.image = UIImage.init(data: data!)
cell.setNeedsLayout()
}
}
} else if let URL = URL(string: imageURL), let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage.init(data: data)
}
cell.textLabel?.text = "sent by: \(name)"
} else {
let text = message[Constants.MessageFields.text] ?? ""
cell.textLabel?.text = name + ": " + text
cell.imageView?.image = UIImage(named: "ic_account_circle")
if let photoURL = message[Constants.MessageFields.photoURL], let URL = URL(string: photoURL),
let data = try? Data(contentsOf: URL) {
cell.imageView?.image = UIImage(data: data)
}
}
return cell
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
guard let text = textField.text else { return true }
textField.text = ""
view.endEditing(true)
let data = [Constants.MessageFields.text: text]
sendMessage(withData: data)
return true
}
func sendMessage(withData data: [String: String]) {
var mdata = data
mdata[Constants.MessageFields.name] = Auth.auth().currentUser?.displayName
if let photoURL = Auth.auth().currentUser?.photoURL {
mdata[Constants.MessageFields.photoURL] = photoURL.absoluteString
}
// Push data to Firebase Database
self.ref.document().setData(mdata, merge: true) { (err) in
if let err = err {
print(err.localizedDescription)
}
print("Successfully set newest city data")
}
}
// MARK: - Image Picker
#IBAction func didTapAddPhoto(_ sender: AnyObject) {
let picker = UIImagePickerController()
picker.delegate = self
if UIImagePickerController.isSourceTypeAvailable(UIImagePickerController.SourceType.camera) {
picker.sourceType = .camera
} else {
picker.sourceType = .photoLibrary
}
present(picker, animated: true, completion:nil)
}
func imagePickerController(_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
picker.dismiss(animated: true, completion:nil)
guard let uid = Auth.auth().currentUser?.uid else { return }
// if it's a photo from the library, not an image from the camera
if #available(iOS 8.0, *), let referenceURL = info[.originalImage] as? URL {
let assets = PHAsset.fetchAssets(withALAssetURLs: [referenceURL], options: nil)
let asset = assets.firstObject
asset?.requestContentEditingInput(with: nil, completionHandler: { [weak self] (contentEditingInput, info) in
let imageFile = contentEditingInput?.fullSizeImageURL
let filePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000))/\((referenceURL as AnyObject).lastPathComponent!)"
guard let strongSelf = self else { return }
strongSelf.storageRef.child(filePath)
.putFile(from: imageFile!, metadata: nil) { (metadata, error) in
if let error = error {
let nsError = error as NSError
print("Error uploading: \(nsError.localizedDescription)")
return
}
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
})
} else {
guard let image = info[.originalImage] as? UIImage else { return }
let imageData = image.jpegData(compressionQuality:0.8)
let imagePath = "\(uid)/\(Int(Date.timeIntervalSinceReferenceDate * 1000)).jpg"
let metadata = StorageMetadata()
metadata.contentType = "image/jpeg"
self.storageRef.child(imagePath)
.putData(imageData!, metadata: metadata) { [weak self] (metadata, error) in
if let error = error {
print("Error uploading: \(error)")
return
}
guard let strongSelf = self else { return }
strongSelf.sendMessage(withData: [Constants.MessageFields.imageURL: strongSelf.storageRef.child((metadata?.path)!).description])
}
}
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
picker.dismiss(animated: true, completion:nil)
}
#IBAction func signOut(_ sender: UIButton) {
let firebaseAuth = Auth.auth()
do {
try firebaseAuth.signOut()
dismiss(animated: true, completion: nil)
} catch let signOutError as NSError {
print ("Error signing out: \(signOutError.localizedDescription)")
}
}
func showAlert(withTitle title: String, message: String) {
DispatchQueue.main.async {
let alert = UIAlertController(title: title,
message: message, preferredStyle: .alert)
let dismissAction = UIAlertAction(title: "Dismiss", style: .destructive, handler: nil)
alert.addAction(dismissAction)
self.present(alert, animated: true, completion: nil)
}
}
}
Edit
perform this block of code on main thread
for doc in documents {
self.messages.append(doc)
self.clientTable.insertRows(at: [IndexPath(row: self.messages.count-1, section: 0)], with: .automatic)
}
This should work..
Good evening,
I have run into the issue of getting the following error message from Cloudkit when trying to save a record to the public database:
"Server Rejected Request" (15/2027); server message = "Custom zones are not allowed in public DB";
I have been able to find the issue causing this error. Within this application I need to fetch zone changes on the private database so I have had to save my records to a custom zone in order to accomplish that fetch.
The following code is where I am saving to the custom zone on the private database:
let zoneID = CKManager.defaultManager.sharedZone?.zoneID
let deckSetToBeSaved = deckName.deckToCKRecord(zoneID)
privateDatabase.save(deckSetToBeSaved) { (record, error) in
DispatchQueue.main.async {
if let record = record {
deckName.cKRecordToDeck(record)
try? self.managedContext.save()
}
}
print("New record saved")
print(error as Any)
}
}
Here is my code for saving a record to the public database:
func shareDeckPlan(_ deck: Deck, completion: #escaping (Bool, NSError?) -> Void ) {
var records = [CKRecord]()
let deckRecord = deck.deckToCKRecord()
records.append(deckRecord)
let reference = CKReference(record: deckRecord, action: .deleteSelf)
for index in deck.cards {
let cardRecord = index.cardToCKRecord()
cardRecord["deck"] = reference
records.append(cardRecord)
}
let operation = CKModifyRecordsOperation(recordsToSave: records, recordIDsToDelete: nil)
operation.modifyRecordsCompletionBlock = {(savedRecords, deletedIDs, error) in
if error == nil {
DispatchQueue.main.async(execute: {
if let savedRecords = savedRecords {
for record in savedRecords {
if record.recordID.recordName == deck.ckRecordID {
deck.cKRecordToDeck(record)
}
for cards in deck.cards {
if record.recordID.recordName == cards.ckRecordID {
cards.ckRecordToCard(record)
}
}
}
}
let _ = try? self.managedContext.save()
DispatchQueue.main.async(execute: {
completion(true, error as NSError?)
})
})
} else {
DispatchQueue.main.async(execute: {
print(error!)
completion(false, error as NSError?)
})
}
}
operation.qualityOfService = .userInitiated
self.publicDatabase.add(operation)
}
The following code referes to the deckToCKRecord method:
func deckToCKRecord(_ zoneID:CKRecordZoneID? = nil) -> CKRecord {
let deckSetName: CKRecord
if let ckMetaData = ckMetaData {
let unarchiver = NSKeyedUnarchiver(forReadingWith: ckMetaData as Data)
unarchiver.requiresSecureCoding = true
deckSetName = CKRecord(coder: unarchiver)!
}
else {
if let zoneID = zoneID {
deckSetName = CKRecord(recordType: "Deck", zoneID: zoneID)
self.ckRecordID = deckSetName.recordID.recordName
} else {
deckSetName = CKRecord(recordType: "Deck")
self.ckRecordID = deckSetName.recordID.recordName
}
}
deckSetName["name"] = name! as CKRecordValue
deckSetName["cardCount"] = cards.count as CKRecordValue
return deckSetName
}
How can I save a record both to the private database in a custom zone and the public database successfully?
I'm working on this subscription system for my app,it took a few days.But as I'm working more on it,it makes more problems.
Sometimes it happens that the app crashes when clicking on buy subscription.
Also I'm having the problem of not knowing when to stop the Please wait.... alert.I need to place the code in:
case .Purchasing:
self.alert_show()
break
But I don't know where to end it,I need to know the information when is the alert from iTunes loaded than to stop the Please wait.... alert.
The biggest problem right now that I'm facing is the crashing sometimes when i click on the Buy Button.
The crashing happens when first time launching the app,and clicking one the Buy Subscription.
Here is the code for the Buy Subscription:
#IBAction func buy_sub(sender: AnyObject) {
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
Here is the error that I'm getting.
fatal error: unexpectedly found nil while unwrapping an Optional value..
I would like to share with you my source code of the subscription.If you have any advices what to add,or to correct or to correct this problems that i have it would be great.
Here is the full source code of the Subscription View:
class SubscriptionViewController: UIViewController ,SKPaymentTransactionObserver, SKProductsRequestDelegate {
//var productID = ""
var product: SKProduct!
#IBOutlet var buy_trial_button: UIButton!
override func viewDidAppear(animated: Bool) {
if(SKPaymentQueue.canMakePayments()) {
} else {
buy_trial_button.enabled = false
message_alert("Please enable IAPS to continue(Credit card information required in your iTunes account).")
}
//validateReceipt()
let keystore = NSUbiquitousKeyValueStore.defaultStore()
if keystore.objectForKey("expiration_date") != nil{
let expiration: AnyObject? = keystore.objectForKey("expiration_date")
let today = NSDate()
let expiredate = expiration as! NSDate
print("expiredate is %#",expiredate,today)
// var date1 = NSDate()
// var date2 = NSDate()
if(today.compare(expiredate) == NSComparisonResult.OrderedDescending){
print("today is later than expiredate")
validateReceipt()
print("Validating")
}else if(today.compare(expiredate) == NSComparisonResult.OrderedAscending){
print("today is earlier than expiredate")
self.performSegueWithIdentifier("subscriptionPassed", sender: self)
}
}else{
print("First time launch")
}
}
override func viewDidLoad() {
super.viewDidLoad()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
self.getProductInfo()
//SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//self.getProductInfo()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
}
#IBAction func private_policy(sender: AnyObject) {
let openLink = NSURL(string : "http://arsutech.com/private_policy.php")
UIApplication.sharedApplication().openURL(openLink!)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "subscriptionPassed")
{
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse){
let products = response.products
if products.count != 0
{
product = products[0] as SKProduct
print(product.localizedTitle + "\n" + product.localizedDescription)
}
}
func getProductInfo(){
if (SKPaymentQueue.canMakePayments()){
let productID:NSSet = NSSet(object: "com.sxxxxxxxxxxxxxxxxx")
let request:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
}
#IBAction func buy_sub(sender: AnyObject) {
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
#IBAction func buy_pro(sender: AnyObject) {
let openLink = NSURL(string : "https://itunes.apple.com/us/app/home-workouts-exercises-mma/id1060747118?mt=8")
UIApplication.sharedApplication().openURL(openLink!)
}
#IBAction func exit(sender: AnyObject) {
exit(0)
}
func validateReceipt(){
alert_show()
let mainBundle = NSBundle.mainBundle() as NSBundle;
let receiptUrl = mainBundle.appStoreReceiptURL;
let isPresent = receiptUrl?.checkResourceIsReachableAndReturnError(NSErrorPointer());
if(isPresent == true){
let data = NSData(contentsOfURL: receiptUrl! );
// Create the JSON object that describes the request
let requestContents = NSMutableDictionary();
//let encodeddata = data!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions());
let encodeddata = data!.base64EncodedString();
//print("encodeddata = \(encodeddata)");
requestContents.setObject(encodeddata, forKey: "receipt-data");
requestContents.setObject("c40f23af1aa44e159aezzzzzzzzzzzzzz", forKey: "password");
var requestData : NSData?
do{
requestData = try NSJSONSerialization.dataWithJSONObject(requestContents, options: NSJSONWritingOptions());
}catch{
// NSLog("Error in json data creation at verifyPaymentReceipt");
}
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let file = "\(documentsPath)/requestData"
if(NSFileManager.defaultManager().createFileAtPath(file, contents: data, attributes: nil)){
NSLog("File %# ",file);
}
else{
//NSLog("error File %# ",file);
}
if(requestData != nil){
//let strRequestData = NSString(data: requestData!, encoding: NSUTF8StringEncoding);
//print(" strRequestData = \(strRequestData)");
// Create a POST request with the receipt data.
//https://buy.itunes.apple.com/verifyReceipt
//https://sandbox.itunes.apple.com/verifyReceipt
let storeURL = NSURL(string: "https://sandbox.itunes.apple.com/verifyReceipt");
let storeRequest = NSMutableURLRequest(URL: storeURL!);
storeRequest.HTTPMethod = "POST";
storeRequest.HTTPBody = requestData;
// Make a connection to the iTunes Store on a background queue.
let queue = NSOperationQueue();
NSURLConnection.sendAsynchronousRequest(storeRequest, queue: queue, completionHandler: { (response : NSURLResponse?, data : NSData?, error : NSError?) -> Void in
if(error != nil){
//Handle Error
}
else{
let d = NSString(data: data!, encoding: NSUTF8StringEncoding);
// NSLog("DATA:%#", d!);
let dataA = d!.dataUsingEncoding(NSUTF8StringEncoding)
var jsonResponseInternal: NSMutableDictionary?
do{
jsonResponseInternal = try NSJSONSerialization.JSONObjectWithData(dataA!,options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
//print(jsonResponseInternal);
}catch{
// NSLog("Parsing issue : verifyPaymentReceipt");
}
var jsonResponse: NSMutableDictionary?
do{
jsonResponse = try NSJSONSerialization.JSONObjectWithData(data!,
options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
//print(jsonResponse);
}catch{
// NSLog("Parsing issue : verifyPaymentReceipt");
}
if(jsonResponse != nil){
if(jsonResponse != nil){
//NSLog("Expiration Date: %#", jsonResponse!);
//print("Passed")
/*
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "Purchase")
NSUserDefaults.standardUserDefaults().synchronize()
*/
//self.performSegueWithIdentifier("subscriptionPassed", sender: self)
if jsonResponse!["status"] as? Int == 0 {
//print("Sucessfully returned internal receipt data")
if let receiptInfo: NSArray = jsonResponse!["latest_receipt_info"] as? NSArray {
let lastReceipt = receiptInfo.lastObject as! NSDictionary
var trial_period: Bool = false
// Get last receipt
//print("LAST RECEIPT INFORMATION \n",lastReceipt)
print("Last from internal memory",lastReceipt["original_transaction_id"])
//var is_trial = lastReceipt["is_trial_period"] as! Bool
//var date_bought =
//var date_expires =
// Format date
//print(is_trial)
// Format date
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
// Get Expiry date as NSDate
let subscriptionExpirationDate: NSDate = formatter.dateFromString(lastReceipt["expires_date"] as! String) as NSDate!
print("\n - DATE SUBSCRIPTION EXPIRES = \(subscriptionExpirationDate)")
let currentDateTime = NSDate()
print(currentDateTime)
if var is_trial:Bool = false{
if(lastReceipt["is_trial_period"] as? String == "Optional(\"true\")"){
is_trial = true
trial_period = is_trial
}else if(lastReceipt["is_trial_period"] as? String == "Optional(\"false\")"){
is_trial = false
trial_period = is_trial
}
}
if (subscriptionExpirationDate.compare(currentDateTime) == NSComparisonResult.OrderedDescending) {
self.alrt_close()
print("Pass");
print("The trial period is \(trial_period)")
let keystore = NSUbiquitousKeyValueStore.defaultStore()
keystore.setObject(subscriptionExpirationDate,forKey:"expiration_date")
keystore.synchronize()
self.performSegueWithIdentifier("subscriptionPassed", sender: self)
} else if (subscriptionExpirationDate.compare(currentDateTime) == NSComparisonResult.OrderedAscending) {
print("Not Pass");
print("Subscription expired")
self.alrt_close()
//self.message_alert("Subscription expired")
}
}
}
}
}
}
});
}
}
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchasing:
//self.alrt_close()
break
case .Deferred:
self.alert_show()
break
case .Purchased:
alrt_close()
self.validateReceipt()
//self.setExpirationDate()
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
//dismissViewControllerAnimated(false, completion: nil)
break
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
print("Not called Expired")
message_alert("Error while purchasing!")
break
case .Restored:
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
print("Restored")
break
default:
break
}
}
}
}
//Alrt
func message_alert(let message_A:String){
let alert = UIAlertController(title: "", message: "\(message_A)", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
func alert_show(){
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .Alert)
alert.view.tintColor = UIColor.blackColor()
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(10, 5, 50, 50)) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
presentViewController(alert, animated: true, completion: nil)
}
func alrt_close(){
self.dismissViewControllerAnimated(false, completion: nil)
}
}