How to write image data and GPS metadata to saved photos album using swift? - swift

I am trying to add GPS metadata to images I take using a custom camera application. My camera app takes images as expected and there are lots of apple specific metadata values included in each picture I take. However I can't seem to add GPS data. What am I a doing wrong? Thanks ahead.
class ViewController: UIViewController {
var output : AVCaptureStillImageOutput!
var locationManager : CLLocationManager()
override func viewDidLoad()
createCamera()
initLocationManager()
super.viewDidLoad()
}
func createCamera(){
//create camera code. works fine
}
func takePhoto(){
self.output.captureStillImageAsynchronousFromConnection(self.connection){
buffer, error in
if let error = error{
println("Error: \(error)")
}
else{
var imageData = AVCaptureStillImageOutput.jpegStillImageNSDataRepresentation(buffer)
var metadata = self.getImageMetaData()
var library = ALAssestsLibrary()
library.writeImageDataToSavedPhotosAlbum(imageData, metadata, completionBlock: {
(assetURL: NSURL!, error: NSError!) -> Void in
})
}
}
}
func getImageMetadata() -> NSMutableDictionary{
var locDict = NSMutableDictioary()
locDict.setObject(self.locationManager.location.timestamp, forKey: "KCGImagePrropertyGPSTimeStamp")
locDict.setObject("N", forKey: kCGImagePropertyGPSLatitudeRef")
locDict.setObject(self.locationManager.location.coordinate.latitude, forKey: "kCGImagePropertyGPSLatitude")
locDict.setObject("W", forKey: "kCGImagePropertyGPSLongitudeRef")
locDict.setObject(self.locationManager.location.coordinate.longitude, forKey: "kCGImagePropertyGPSLongitude")
return locDict
//this returns the correct coordinates
//NOTE: I hard coded the Ref values for brevity.
}
}
NOTE: I am using ExifTool to view the metadata.

Related

implementation of NSMetadataQuery along with UIDocuments in swiftUI

I am trying to make a document based app in swiftUI with a custom UI. I want iCloud capabilities in my app. I am trying to use iCloud Document (No cloudKit) way for storing data on iCloud container. I am using UIDocument and it's working. It's storing data to iCloud and I am able to retrieve it back.
Now the thing is when I run the app on two devices (iphone and iPad) and make changes to a file on one device, the changes are not reflecting on the other device while the file or say app is open. I have to close the app and relaunch it to see the changes.
I know I have to implement NSMetadataQuery to achieve this but I am struggling with it. I don't know any objective-C. I have been searching on the internet for a good article but could not find any. Can you please tell how do I implement this feature in my app. I have attach the working code of UIDocument and my Model class.
Thank you in advance !
UIDocument
class NoteDocument: UIDocument {
var notes = [Note]()
override func load(fromContents contents: Any, ofType typeName: String?) throws {
if let contents = contents as? Data {
if let arr = try? PropertyListDecoder().decode([Note].self, from: contents) {
self.notes = arr
return
}
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -1, userInfo: nil)
}
override func contents(forType typeName: String) throws -> Any {
if let data = try? PropertyListEncoder().encode(self.notes) {
return data
}
//if we get here, there was some kind of problem
throw NSError(domain: "NoDataDomain", code: -2, userInfo: nil)
}
}
Model
class Model: ObservableObject {
var document: NoteDocument?
var documentURL: URL?
init() {
let fm = FileManager.default
let driveURL = fm.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents")
documentURL = driveURL?.appendingPathComponent("savefile.txt")
document = NoteDocument(fileURL: documentURL!)
}
func loadData(viewModel: ViewModel) {
let fm = FileManager.default
if fm.fileExists(atPath: (documentURL?.path)!) {
document?.open(completionHandler: { (success: Bool) -> Void in
if success {
viewModel.notes = self.document?.notes ?? [Note]()
print("File load successfull")
} else {
print("File load failed")
}
})
} else {
document?.save(to: documentURL!, for: .forCreating, completionHandler: { (success: Bool) -> Void in
if success {
print("File create successfull")
} else {
print("File create failed")
}
})
}
}
func saveData(_ notes: [Note]) {
document!.notes = notes
document?.save(to: documentURL!, for: .forOverwriting, completionHandler: { (success: Bool) -> Void in
if success {
print("File save successfull")
} else {
print("File save failed")
}
})
}
func autoSave(_ notes: [Note]) {
document!.notes = notes
document?.updateChangeCount(.done)
}
}
Note
class Note: Identifiable, Codable {
var id = UUID()
var title = ""
var text = ""
}
This is a complex topic. Apple do provide some sample swift code, the Document-Based App Programming Guide for iOS and iCloud Design Guide.
There is also some good third party guidance: Mastering the iCloud Document Store.
I would recommend reading the above, and then return to the NSMetaDataQuery API. NSMetaDataQuery has an initial gathering phase and a live-update phase. The later phase can remain in operation for the lifetime of your app, allowing you to be notified of new documents in your app's iCloud container.

How to capture depth data from camera in iOS 11 and Swift 4?

I'm trying to get depth data from the camera in iOS 11 with AVDepthData, tho when I setup a photoOutput with the AVCapturePhotoCaptureDelegate the photo.depthData is nil.
So I tried setting up the AVCaptureDepthDataOutputDelegate with a AVCaptureDepthDataOutput, tho I don't know how to capture the depth photo?
Has anyone ever got an image from AVDepthData?
Edit:
Here's the code I tried:
// delegates: AVCapturePhotoCaptureDelegate & AVCaptureDepthDataOutputDelegate
#IBOutlet var image_view: UIImageView!
#IBOutlet var capture_button: UIButton!
var captureSession: AVCaptureSession?
var sessionOutput: AVCapturePhotoOutput?
var depthOutput: AVCaptureDepthDataOutput?
var previewLayer: AVCaptureVideoPreviewLayer?
#IBAction func capture(_ sender: Any) {
self.sessionOutput?.capturePhoto(with: AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg]), delegate: self)
}
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
self.previewLayer?.removeFromSuperlayer()
self.image_view.image = UIImage(data: photo.fileDataRepresentation()!)
let depth_map = photo.depthData?.depthDataMap
print("depth_map:", depth_map) // is nil
}
func depthDataOutput(_ output: AVCaptureDepthDataOutput, didOutput depthData: AVDepthData, timestamp: CMTime, connection: AVCaptureConnection) {
print("depth data") // never called
}
override func viewDidLoad() {
super.viewDidLoad()
self.captureSession = AVCaptureSession()
self.captureSession?.sessionPreset = .photo
self.sessionOutput = AVCapturePhotoOutput()
self.depthOutput = AVCaptureDepthDataOutput()
self.depthOutput?.setDelegate(self, callbackQueue: DispatchQueue(label: "depth queue"))
do {
let device = AVCaptureDevice.default(for: .video)
let input = try AVCaptureDeviceInput(device: device!)
if(self.captureSession?.canAddInput(input))!{
self.captureSession?.addInput(input)
if(self.captureSession?.canAddOutput(self.sessionOutput!))!{
self.captureSession?.addOutput(self.sessionOutput!)
if(self.captureSession?.canAddOutput(self.depthOutput!))!{
self.captureSession?.addOutput(self.depthOutput!)
self.previewLayer = AVCaptureVideoPreviewLayer(session: self.captureSession!)
self.previewLayer?.frame = self.image_view.bounds
self.previewLayer?.videoGravity = AVLayerVideoGravity.resizeAspectFill
self.previewLayer?.connection?.videoOrientation = AVCaptureVideoOrientation.portrait
self.image_view.layer.addSublayer(self.previewLayer!)
}
}
}
} catch {}
self.captureSession?.startRunning()
}
I'm trying two things, one where the depth data is nil and one where I'm trying to call a depth delegate method.
Dose anyone know what I'm missing?
First, you need to use the dual camera, otherwise you won't get any depth data.
let device = AVCaptureDevice.default(.builtInDualCamera, for: .video, position: .back)
And keep a reference to your queue
let dataOutputQueue = DispatchQueue(label: "data queue", qos: .userInitiated, attributes: [], autoreleaseFrequency: .workItem)
You'll also probably want to synchronize the video and depth data
var outputSynchronizer: AVCaptureDataOutputSynchronizer?
Then you can synchronize the two outputs in your viewDidLoad() method like this
if sessionOutput?.isDepthDataDeliverySupported {
sessionOutput?.isDepthDataDeliveryEnabled = true
depthDataOutput?.connection(with: .depthData)!.isEnabled = true
depthDataOutput?.isFilteringEnabled = true
outputSynchronizer = AVCaptureDataOutputSynchronizer(dataOutputs: [sessionOutput!, depthDataOutput!])
outputSynchronizer!.setDelegate(self, queue: self.dataOutputQueue)
}
I would recommend watching WWDC session 507 - they also provide a full sample app that does exactly what you want.
https://developer.apple.com/videos/play/wwdc2017/507/
To give more details to #klinger answer, here is what you need to do to get Depth Data for each pixel, I wrote some comments, hope it helps!
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
//## Convert Disparity to Depth ##
let depthData = (photo.depthData as AVDepthData!).converting(toDepthDataType: kCVPixelFormatType_DepthFloat32)
let depthDataMap = depthData.depthDataMap //AVDepthData -> CVPixelBuffer
//## Data Analysis ##
// Useful data
let width = CVPixelBufferGetWidth(depthDataMap) //768 on an iPhone 7+
let height = CVPixelBufferGetHeight(depthDataMap) //576 on an iPhone 7+
CVPixelBufferLockBaseAddress(depthDataMap, CVPixelBufferLockFlags(rawValue: 0))
// Convert the base address to a safe pointer of the appropriate type
let floatBuffer = unsafeBitCast(CVPixelBufferGetBaseAddress(depthDataMap), to: UnsafeMutablePointer<Float32>.self)
// Read the data (returns value of type Float)
// Accessible values : (width-1) * (height-1) = 767 * 575
let distanceAtXYPoint = floatBuffer[Int(x * y)]
}
There are two ways to do this, and you are trying to do both at once:
Capture depth data along with the image. This is done by using the photo.depthData object from photoOutput(_:didFinishProcessingPhoto:error:). I explain why this did not work for you below.
Use a AVCaptureDepthDataOutput and implement depthDataOutput(_:didOutput:timestamp:connection:). I am not sure why this did not work for you, but implementing depthDataOutput(_:didOutput:timestamp:connection:) might help you figure out why.
I think that #1 is a better option, because it pairs the depth data with the image. Here's how you would do that:
#IBAction func capture(_ sender: Any) {
let settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.jpeg])
settings.isDepthDataDeliveryEnabled = true
self.sessionOutput?.capturePhoto(with: settings, delegate: self)
}
// ...
override func viewDidLoad() {
// ...
self.sessionOutput = AVCapturePhotoOutput()
self.sessionOutput.isDepthDataDeliveryEnabled = true
// ...
}
Then, depth_map shouldn't be nil. Make sure to read both this and this (separate but similar pages) for more information about obtaining depth data.
For #2, I'm not quite sure why depthDataOutput(_:didOutput:timestamp:connection:) isn't being called, but you should implement depthDataOutput(_:didDrop:timestamp:connection:reason:) to see if depth data is being dropped for some reason.
The way you init your capture device is not right.
You should use the dual camera mode.
as for oc like follows:
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithDeviceType:AVCaptureDeviceTypeBuiltInDualCamera mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionBack];

Getting Photo Data from AVCapturePhotoCaptureDelegate

I'm currently trying to build a camera application for fun and ran into a problem.
I was using an example that Apple provides you for the process of taking a photo or video. Found here:
https://developer.apple.com/library/content/samplecode/AVCam/Introduction/Intro.html
I'm having a problem using the PhotoCaptureDelegate. So currently when the user takes a photo it will save it to the users photo library. What I would like to do, is after it captures the photo I want to store the photoData Object of the photo. So that I can display the photo on another view as a preview before you save it.
This is what Apple suggests to use:
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
if let error = error {
print("Error capturing photo: \(error)")
didFinish()
return
}
guard let photoData = photoData else {
print("No photo data resource")
didFinish()
return
}
PHPhotoLibrary.requestAuthorization { [unowned self] status in
if status == .authorized {
PHPhotoLibrary.shared().performChanges({ [unowned self] in
let creationRequest = PHAssetCreationRequest.forAsset()
creationRequest.addResource(with: .photo, data: photoData, options: nil)
if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL {
let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions()
livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true
creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions)
}
}, completionHandler: { [unowned self] success, error in
if let error = error {
print("Error occurered while saving photo to photo library: \(error)")
}
self.didFinish()
}
)
}
else {
self.didFinish()
}
}
}
How would I take the photoData object from my delegate and pass it back to my view controller, that is calling on this delegate?
There are a couple ways to solve this. You could also have your view controller own the photo capture delegate (like Apple's example) and pass it back up after the photo is taken. You could also just have your view controller be the photo capture delegate and not worry about passing anything!
1) Pass the photo back up
There are (again) a couple ways to do this. You could have the delegate pass the value back up to the controller. You could also have the delegate tell the controller it is done, and have the controller come grab the photo. I'll explain the second way below.
Looking at Apple's example, we can see that the PhotoCaptureDelegate already tells the controller when it is done! Whenever a PhotoCaptureDelegate object is created, it is passed a completed block.
To to allow the controller to grab the photo, we just have to make the photo object public!
Change the private var photoData: Data? = nil in PhotoCaptureDelegate to be public var photoData: Data? = nil
In CameraViewController when you define the completed block, you now have the photo data:
...
, completed: { [unowned self] photoCaptureDelegate in
self.sessionQueue.async { [unowned self] in
self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = nil
if let photoData = photoCaptureDelegate.photoData {
// You now have the photo data!
// You can pass this to another method in the Controller now
}
}
}
...
2) View Controller as the AVCapturePhotoCaptureDelegate
Another way to solve this is to just have your ViewController be the AVCapturePhotoCaptureDelegate. Then you'll already have the photo data in the view controller! It could look something like this:
class MyViewController: UIViewController, AVCapturePhotoCaptureDelegate {
...
func capture(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
guard let photoData = photoData else {
return
}
// You now have your photo data!
}
}

Swift - Core Data student info storage app errors

EDIT - i've played around with the code; still having issues.
i've been trying to modify the app in this tutorial so that i can enter student information and store it using core data. Ideally, i would like to be able to display that information on a label; but i haven't gotten that far yet. This is my first time working with core data and currently, i've hit a wall and need some assistance figuring out where in my code i've gone wrong and what to do to get it working.
So my questions are, how do i fix these errors.
and How would i got about displaying all the data after it's saved on a label.
Updated Screenshot
Updated Error Screenshot
Thanks in advance!
Code :
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet var name: UITextField!
#IBOutlet var address1: UITextField!
#IBOutlet var address2: UITextField!
#IBOutlet var city: UITextField!
#IBOutlet var state: UITextField!
#IBOutlet var zip: UITextField!
#IBOutlet var grade: UITextField!
#IBOutlet var status: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
#IBAction func insertStudent(_ sender: AnyObject) {
let context = getContext()
let entityDescription = NSEntityDescription.entity(forEntityName: "Contacts", in: context)
let contact = NSManagedObject(entity: entityDescription!, insertInto: context) as! Contacts
contact.student_name = name.text
contact.address1 = address1.text
contact.address2 = address2.text
contact.city = city.text
contact.grade = grade.text
contact.state = state.text
contact.zip = zip.text
var error: NSError?
//save the object
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Looks like you are trying to mix Swift versions. See code examples below and see if this helps clear up the issue.
AppDelegate
import UIKit
import CoreData
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
return true
}
func applicationWillResignActive(_ application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
}
func applicationDidEnterBackground(_ application: UIApplication) {
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
}
func applicationWillEnterForeground(_ application: UIApplication) {
// Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
}
func applicationDidBecomeActive(_ application: UIApplication) {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
}
func applicationWillTerminate(_ application: UIApplication) {
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
// Saves changes in the application's managed object context before the application terminates.
self.saveContext()
}
// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "CDTest")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
// Replace this implementation with code to handle the error appropriately.
// fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
}
Functions to save and get data from CoreData in Swift 3.
func storeTranscription() {
let context = getContext()
//retrieve the entity that we just created
let entity = NSEntityDescription.entity(forEntityName: "ItemList", in: context)
let transc = NSManagedObject(entity: entity!, insertInto: context) as! ItemList
//set the entity values
transc.itemID = Double(itemid)
transc.productname = nametext
transc.amount = Double(amountDouble)
transc.stock = stockStatus
transc.inventoryDate = inventoryDate
//save the object
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}
func getTranscriptions () {
//create a fetch request, telling it about the entity
let fetchRequest: NSFetchRequest<ItemList> = ItemList.fetchRequest()
do {
//go get the results
let searchResults = try getContext().fetch(fetchRequest)
fetchedStatsArray = searchResults as [NSManagedObject]
//I like to check the size of the returned results!
print ("num of results = \(searchResults.count)")
//You need to convert to NSManagedObject to use 'for' loops
for trans in searchResults as [NSManagedObject] {
//get the Key Value pairs (although there may be a better way to do that...
print("\(trans.value(forKey: "productname")!)")
let mdate = trans.value(forKey: "inventoryDate") as! Date
print(mdate)
}
} catch {
print("Error with request: \(error)")
}
}
Your Code converted Swift 3
func getContext () -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
#IBAction func insertStudent(_ sender: AnyObject) {
let context = getContext()
let entityDescription = NSEntityDescription.entity(forEntityName: "Contacts", in: context)
let contact = NSManagedObject(entity: entityDescription!, insertInto: context) as! Contacts
contact.student_name = name.text
contact.address1 = address1.text
contact.address2 = address2.text
contact.city = city.text
contact.grade = grade.text
contact.state = state.text
contact.zip = zip.text
var error: NSError?
//save the object
do {
try context.save()
print("saved!")
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
} catch {
}
}

How to make High Score of game to be saved on Leaderboard, with Swift?

I made a game using SpriteKit and Xcode 7 beta. I tried to put GameCenter and Leaderboard but the problem is that the score in leaderboard won't change it stay all the time 0 (High Score of game won't save in Leaderboard) and I don't know how to fix it. I'm using 3 different files: GameScene.swift, GameViewController.swift, and PointsLabel.swift.
GameScene.swift
func addPointsLabels() {
let pointsLabel = PointsLabel(num: 0)
pointsLabel.position = CGPointMake(30.0, view!.frame.size.height - 40)
pointsLabel.name = "pointsLabel"
addChild(pointsLabel)
//High Score
let highscoreLabel = PointsLabel(num: 0)
highscoreLabel.name = "highscoreLabel"
highscoreLabel.position = CGPointMake(view!.frame.size.width - 35, view!.frame.size.height - 40)
addChild(highscoreLabel)
}
func loadHighscore() {
let defaults = NSUserDefaults.standardUserDefaults()
let highscoreLabel = childNodeWithName("highscoreLabel") as! PointsLabel
highscoreLabel.setTo(defaults.integerForKey("highscore"))
}
GameViewController.swift:
import GameKit
class GameViewController: UIViewController,UIGestureRecognizerDelegate, GKGameCenterControllerDelegate {
var scoreManager = PointsLabel(num: 0)
override func viewDidLoad() {
super.viewDidLoad()
//initiate gamecenter
func authenticateLocalPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {(GameViewController, error) -> Void in
if (GameViewController != nil) {
self.presentViewController(GameViewController!, animated: true, completion: nil)
}
else {
print((GKLocalPlayer.localPlayer().authenticated))
}
}
}
}
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(scoreManager.score)
scoreManager.increment()
showLeader()
}
//send high score to leaderboard
func saveHighscore(score:Int) {
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "Leaderboard_01")
scoreReporter.value = Int64(score)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("error")
}
})
}
}
//shows leaderboard screen
func showLeader() {
let vc = self.view?.window?.rootViewController
let gc = GKGameCenterViewController()
gc.gameCenterDelegate = self
vc?.presentViewController(gc, animated: true, completion: nil)
}
}
//hides leaderboard screen
func gameCenterViewControllerDidFinish(gameCenterViewController: GKGameCenterViewController)
{
gameCenterViewController.dismissViewControllerAnimated(true, completion: nil)
}
PointsLabel.swift:
import Foundation
import UIKit
import SpriteKit
class PointsLabel: SKLabelNode {
var score:Int = 0
init(num: Int) {
super.init()
fontColor = UIColor.blackColor()
fontSize = 30.0
score = num
text = "\(num)"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func increment() {
score++
text = "\(score)"
}
func setTo(num: Int) {
self.score = num
text = "\(self.score)"
}
}
I think the problem is in file GameViewController.swift in code:
#IBAction func leaderboard(sender: UIButton) {
saveHighscore(scoreManager.score)
scoreManager.increment() //<-- Here
showLeader()
}
Maybe I didn't put it in right place
scoreManager.increment()
Well I see a couple things that can contribute to the issue. First is this method...
func loadHighscore() {
let defaults = NSUserDefaults.standardUserDefaults()
let highscoreLabel = childNodeWithName("highscoreLabel") as! PointsLabel
highscoreLabel.setTo(defaults.integerForKey("highscore"))
}
I don't see anywhere you are setting that so pulling it out won't help much. I added the saving to defaults in the saveHighscore: function bellow.
Second is...
saveHighscore(scoreManager.score)
scoreManager.increment() //<-- Here
showLeader()
You should increment before you save your score.
I would try adding these logs to see if this helps...
func saveHighscore(score:Int) {
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(score, forKey: "highscore")
defaults.synchronize()
//check if user is signed in
if GKLocalPlayer.localPlayer().authenticated {
println("authenticated")
let scoreReporter = GKScore(leaderboardIdentifier: "Leaderboard_01")
println("ScoreReporter: \(scoreReporter)")
scoreReporter.value = Int64(score)
let scoreArray: [GKScore] = [scoreReporter]
GKScore.reportScores(scoreArray, withCompletionHandler: {error -> Void in
if error != nil {
print("error")
}
else{
println("reported correctly")
}
})
}
}
Hopefully what does or don't print out in the logs plus actually saving your defaults will help. Good luck.
Edit
So it appears the root of the problem is that you have scoreManager (which is a PointsLabel) in your VC but you also have one that is in your Scene. The one in your scene you are updating the score and life is good. When you hit the button you are actually getting the score from the label in your VC that isn't getting updated. So what you really need is to get to that label in your scene to pull out the score.
So the easiest way I can think of getting it to work properly with as little changes to your code is this…
Remove this line completely…
var scoreManager = PointsLabel(num: 0)
and change your action to this...
#IBAction func leaderboard(sender: UIButton) {
let skView = self.view as! SKView
let scene = skView.scene
let scoreManager = scene.childNodeWithName("pointsLabel") as! PointsLabel
saveHighscore(scoreManager.score)
showLeader()
}
Hopefully that fixes everything =)
In your game, you need to increment the score AND send the score to Game Center whenever the user scores a point. I'm not sure how your game works, but whenever they should be awarded a point you need to update the score and send that score to game center and update the label. You just can't update the score when the leaderboard button is tapped. I'm not sure if I was clear so ask if you need clarification or help.
It sounds like you need to use core data, it is what you use to save data in an app. I don't know to much about it so I can only point you to a few recourses that might help:
Apple Docs
Core Data relationships (swift)
Swift - Core Data Seeding Class
Check if property is set in Core Data?
How do I access and use an entity using Core Data