Fetch Record Assets in CloudKit Using Swift - ios

I am new to CloudKit and I am having trouble connecting the assets in my database to the ImageView and TextView in my app. In my CloudKit database, under record types, I created a record named "Companies". I then went to Public Data->Default Zone and then created a new instance of the record called "myCompany". In "myCompany" I have two assets, an image and a .txt file. I want to connect those assets to my app. I am not sure if I need to use CKQuery or what is the best approach. Any advice would be much appreciated. Below is what I have so far. Feel free to give feedback of what I have or if there's a better way, I would love to know. Thanks.
import UIKit
import CloudKit
class LearnViewController: UIViewController {
#IBOutlet weak var theImage: UIImageView!
#IBOutlet weak var theText: UITextView!
let myRecord = CKRecord(recordType: "Companies" )
var image: UIImage!
let database = CKContainer.defaultContainer().publicCloudDatabase
func loadCoverPhoto(completion:(photo: UIImage!) -> ()) {
dispatch_async(
dispatch_get_global_queue(
DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)){
var image: UIImage!
let coverPhoto = self.myRecord.objectForKey("Picture") as CKAsset!
if let asset = coverPhoto {
if let url = asset.fileURL {
let imageData = NSData(contentsOfFile: url.path!)!
image = UIImage(data: imageData)
}
}
completion(photo: image)
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadCoverPhoto() { photo in
dispatch_async(dispatch_get_main_queue()) {
self.theImage.image = photo
}
}

You could get the record directly if you know the recordId by performing a fetch like:
database.fetchRecordWithID(CKRecordID(recordName: recordId), completionHandler: {record, error in
Or if you don't know the ID you should query for the record with something like the code below. just create the right NSPredicate. A query can return more than 1 record.
var query = CKQuery(recordType: recordType, predicate: NSPredicate(value: true))
var operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = { record in
// is this your record...
}
operation.queryCompletionBlock = { cursor, error in
self.handleCallback(error, errorHandler: {errorHandler(error: error)}, completionHandler: {
// ready fetching records
})
}
operation.resultsLimit = CKQueryOperationMaximumResults;
database.addOperation(operation)
In your case when using the fetchRecordWithID option the code would look like this:
override func viewDidLoad() {
super.viewDidLoad()
database.fetchRecordWithID(CKRecordID(recordName: recordId), completionHandler: {record, error in
self.myRecord = record
loadCoverPhoto() { photo in
dispatch_async(dispatch_get_main_queue()) {
self.theImage.image = photo
}
}
}
}

Related

Parse JSON Result To UILabel in Swift

I'm really new into swift & currently learning API by doing a project that shows list of games from rawg.io referring to the website's doc. I created GameFeed.swift & GameDetail.swift to pull name, release date, and rating from it and working fine in my console.
GameFeed.swift :
struct GameFeed: Codable {
let results:[GameDetail]
}
GameDetail.swift :
struct GameDetail: Codable {
let name:String
let released:String
let rating:Double
}
Now i'm trying to put the results to a simple UIlabel like gameName.text, gameReleased.text & gameRating.text from ViewController.swift so it will be show in Main.Storyboard
i did research on google about how to show it to these UIlabel by using DispatchQueue.main.async but when i'm declaring it, it receiving error :
Value of type 'GameFeed' has no member 'name'
same error messages also happened to released & rating. This is my ViewController.Swift :
class ViewController: UIViewController {
#IBOutlet weak var gameName: UILabel!
#IBOutlet weak var gameReleased: UILabel!
#IBOutlet weak var gameRating: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Defining API Site
let urlString = "https://api.rawg.io/api/games"
let url = URL(string: urlString)
guard url != nil else {
return
}
// Calling API
let session = URLSession.shared
let dataTask = session.dataTask(with: url!){
(data, response, error) in
if error == nil && data != nil {
let decoder = JSONDecoder()
do {
let gameFeed = try decoder.decode(GameFeed.self, from: data!)
print(gameFeed)
DispatchQueue.main.async {
self.gameName.text = gameFeed.name
self.gameReleased.text = gameFeed.released
self.gameRating.text = gameFeed.rating
}
}
catch {
print("Error Parsing JSON")
}
}
}
dataTask.resume()
}
}
What should i do to make it possible to parse the data to labels?
The GameFeed contains an Array of GameDetails. But you are trying to set a single GameDetail on those labels. You should first pull out a single GameDetail from that array, then assign it in a way you like.
DispatchQueue.main.async {
let gameDetail = gameFeed.results.first // <- This will return the first one
self.gameName.text = gameDetail?.name
self.gameReleased.text = gameDetail?.released
self.gameRating.text = gameDetail?.rating
}

iOS Swift & Parse - getDataInBackground not working?

Currently learning Swift & iOS. I try to access with Parse a saved picture. However, I can't access it with getDataInBackground(block:).
Here's my code:
//
// ViewController.swift
// Instragram
//
// Created by Macbook Pro on 22.07.17.
// Copyright © 2017 Macbook Pro. All rights reserved.
//
import UIKit
import Parse
class ViewController: UIViewController {
#IBOutlet weak var picture: UIImageView!
#IBOutlet weak var senderLbl: UILabel!
#IBOutlet weak var recieverLbl: UILabel!
#IBOutlet weak var messageLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Creating tables and data in database
let imageData = UIImageJPEGRepresentation(picture.image!, 0.5)
let file = PFFile(name: "picture.jpg", data: imageData!)
let table = PFObject(className: "messages")
table["sender"] = "Akhmed"
table["reciver"] = "Bob"
table["picture"] = file
table["message"] = "Hello!"
table.saveInBackground {(success, error) -> Void in
if(success){
print("Saved successful")
} else {
print(error!)
}
}
//Recieving Data from the Server
let information = PFQuery(className: "messages")
information.findObjectsInBackground{(objects: [PFObject]?, error) -> Void in
if error == nil {
for object in objects!{
self.senderLbl.text = object["sender"] as? String
self.recieverLbl.text = object["reciver"] as? String
self.messageLbl.text = object["message"] as? String
object["picture"].getDataInBackground(...)
}
} else {
print(error!)
}
}
}
}
Down after I access the name, receiver and message string I try to access an image that has been saved on there server with:
object["picture"].getDataInBackground(block:)
However, Swift won't even autocorrect anymore after I've typed object["picture"]. I get also an error:
'Value of type "Any" has no Member 'getDataInBackground(block:)'
Any ideas what's wrong? It seems to me that Swift can't find the string picture even though the image is saved on the server under the string "picture".
You need to first cast it as a PFFfile object and then retrieve the actual image data with getDataInBackground function like this:
let imageFile = object["picture"] as? PFFile
imageFile?.getDataInBackground (block: { (data, error) -> Void in
if error == nil {
if let imageData = data {
self.myImage = UIImage(data:imageData)
}
}
})

How to Utilize Fetched Record Attributes with CloudKit?

so I'm trying to query my public database for a single record using CloudKit, and save an attribute value from the record to an external array(foodArrayLunch). I'm using the property RecordFetchedBlock in a closure to process my record. However, when the closure executes successfully, I find that my array failed to save the attribute value that was queried from the database, and it can be accessed and managed only from inside the closure. So my question is: How can I save this attribute value from outside of the RecordFetchedBlock? Any help would be greatly appreciated, Thanks!
class ViewControllerNewcomb: UIViewController, UITableViewDelegate, UITableViewDataSource {
let database = CKContainer.defaultContainer().publicCloudDatabase
#IBOutlet weak var LunchView: UITableView!
var foodArrayLunch: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
let predicate = NSPredicate(format: "TRUEPREDICATE", argumentArray: nil)
let query = CKQuery(recordType: "FoodData", predicate: predicate)
// get just one value only
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["FoodList"]
// get query
operation.recordFetchedBlock = { (record : CKRecord) in
let menu = record.objectForKey("FoodList") as! [String]
for item in menu {
self.foodArrayLunch.append(item)
}
}
// operation completed
operation.queryCompletionBlock = {(cursor, error) in
dispatch_async(dispatch_get_main_queue()) {
if error == nil {
print("no errors")
} else {
print("error description = \(error?.description)")
}
}
}
database.addOperation(operation)

Swift: How to cancel a PFQuery if function is run again before PFQuery is finished?

Apologies that I couldn't think of a better way to title this.
Basically I have an app which connects to Parse. I have specified an action, which runs when a text field is changed (i.e when the user types a letter)
within this action I'm calling a PFQuery to Parse, which I then ask to findObjectsInBackgroundWithBlock.
The problem is that if a user were to type another letter before this query has finished running, then 2 queries are now running and the results of both end up populating the tableView.
So my question is simply, if the user were to type in another letter before the first findObjectsInBackgroundWithBlock has finished, how would I cancel the first and run a new one?
I have tried inserting PFQuery.cancel(query) at the start of the action, but the code gets confused as there isn't a query running yet when the action runs for the first time.
My code, incase it may help:
#IBAction func textFieldChanged (sender: AnyObject) {
let query = PFUser.Query()
query!.whereKey("Postcode", containsString: searchField.text)
query?.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.citiesArray.append(object["city"] as! String)
}
}
})
Many thanks for your patience!
You can try wrapping those requests in NSOperation and adding them to a dedicated (for such search requests) NSOperationQueue and calling cancelAllOperations() when a new character is typed.
In NSOperation's inner Parse block check for self.cancelled and return doing nothing if cancelled. Should work fine.
UPDATE:
class ViewController: UIViewController {
#IBOutlet weak var searchField: UITextField!
var citiesArray = [String]()
lazy var textRequestQueue: NSOperationQueue = {
var queue = NSOperationQueue()
queue.maxConcurrentOperationCount = 1
queue.qualityOfService = NSQualityOfService.UserInteractive
return queue
}()
#IBAction func textFieldChanged(sender: AnyObject) {
textRequestQueue.cancelAllOperations()
let query = PFQuery()
query.whereKey("Postcode", containsString: searchField.text)
textRequestQueue.addOperation(TextRequestOperation(query: query, resultBlock: { (objects, error) -> Void in
if let objects = objects {
for object in objects {
self.citiesArray.append(object["city"] as! String)
}
}
}))
}
}
class TextRequestOperation: NSOperation {
typealias ResultBlock = ((result: String)->())
var _resultBlock: PFArrayResultBlock
var _query: PFQuery
init(query: PFQuery, resultBlock: PFArrayResultBlock) {
self._resultBlock = resultBlock
self._query = query
}
override func main()
{
if self.cancelled { return }
_query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if self.cancelled { return }
self._resultBlock(objects, error)
}
}
}
NSOperation is one option. ReativeCocoa is another option that could help you solve this problem quite easily if you know how to use it.
However the easiest way (and hackiest) would prob be to keep some state of the search and use it to only apply the most recent searches results.
var mostRecentSearchQuery: String = ""
#IBAction func textFieldChanged (sender: AnyObject) {
var queryString: String?
if let textField: UITextField = sender as? UITextField {
queryString = textField.text
self.mostRecentSearchQuery = textField.text
}
let query = PFUser.Query()
query!.whereKey("Postcode", containsString: searchField.text)
query?.findObjectsInBackgroundWithBlock({[weak self] (objects, error) -> Void in
if let objects = objects where queryString == self?.mostRecentSearchQuery {
for object in objects {
self.citiesArray.append(object["city"] as! String)
}
}
})
This will only update your results if block used the most recent text typed.
ps. I am assuming the sender passed into the method is the textField which text has changed.

How do you query video files from parse.com

I know how to to retrieve an image file from Parse and store it in an UIImageView but how do you retrieve a video(or movie) file from Parse.com and store it in a UIView to be played (using AVPlayer) ?
Import the following in your file:
import Parse
import AVFoundation
import AVKit
add this before your class definition so that you can access it everywhere as global variables:
public var audioPlayer = AVPlayer()
public var songNumber = Int()
add this to your class definition: AVAudioPlayerDelegate
Set arrays to store the data:
var idArray = [String]()
var nameArray = [String]()
Your viewDidLoad + getSongs function:
override func viewDidLoad() {
super.viewDidLoad()
var objectQuery = PFQuery(className: "Songs")
objectQuery {
(objectsArray: [PFObject]?, error: NSError?) -> Void in
var objectIDs = objectsArray!
print(objectIDs)
for i in 0...objectIDs.count-1 {
self.iDArray.append(objectIDs[i].valueForKey("objectId") as! String)
self.nameArray.append(objectIDs[i].valueForKey("songName") as! String)
self.tableView.reloadData()
}
}
}
func getSongs () {
var songQuery = PFQuery(className: "Songs")
songQuery.getObjectInBackgroundWithId(idArray[songNumber], block: {
(object: PFObject?, error : NSError?) -> Void in
if let audioFile = object?["songFile"] as? PFFile {
let audioFileUrlString: String = audioFile.url
let audioFileUrl = NSURL(string: audioFileUrlString)!
audioPlayer = AVPlayer(URL: audioFileUrl)
audioPlayer.play()
}
})
}
After this code, you can get your getSongs function where you want, or if its a TableView then you can set it in a cell with indexPath.row
As the question is a bit broad Im giving a broad answer as I'm not sure how you want to set it and where you want to play it/under what trigger?
Here is a related question I referenced to get you started as well as the docs in Parse.
Even though the question may seem unrelated, the answer and the context is in this question/answer: Could not cast value of type PFFile to NSURL This link shows you how to query and convert so that you can play a music file.
You should also check out the Parse docs: File Queries Parse Docs

Resources