WatchKit CoreLocation issue - core-location

I am trying to read current location coordinates in WatchKitExtension ExtensionDelegate. This does though not return any value.
The very same code used in WatchKitExtension InterfaceController does return the location. (tried this out of desperation as I could not find an error in the code)
I would need to perform this code in ExtensionDelegate as I would like to pass the retrieved location on to a ClockKit Complication.
Here the code in ExtensionDelegate: (after self.locationManager.requestLocation() the delegate functions didUpdateLocation / didFailWithError do not get called)
import WatchKit
import CoreLocation
class ExtensionDelegate: NSObject, WKExtensionDelegate, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override init() {
print("ExtensionDelegate: \(NSDate()) - init")
super.init()
self.getLocation()
}
func getLocation(){
print("ExtensionDelegate: \(NSDate()) - getLocation")
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
...
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("ExtensionDelegate: \(NSDate()) - locationManager didUpdateLocations")
guard let mostRecentLocation = locations.last else { return }
let place = mostRecentLocation.coordinate
print("\(place)")
manager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("ExtensionDelegate: \(NSDate()) - locationManager didFailWithError")
print("CL failed: \(error)")
}
}
Here the very same code in InterfaceController and it works perfectly (didUpdateLocation does get called):
import WatchKit
import Foundation
import CoreLocation
class InterfaceController: WKInterfaceController, CLLocationManagerDelegate {
private let locationManager = CLLocationManager()
override func awakeWithContext(context: AnyObject?) {
print("InterfaceController: \(NSDate()) - awakeWithContext")
super.awakeWithContext(context)
self.getLocation()
}
func getLocation(){
print("InterfaceController: \(NSDate()) - getLocation")
locationManager.delegate = self
locationManager.requestAlwaysAuthorization()
locationManager.requestWhenInUseAuthorization()
locationManager.requestLocation()
}
...
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
print("InterfaceController: \(NSDate()) - locationManager didUpdateLocations")
guard let mostRecentLocation = locations.last else { return }
let place = mostRecentLocation.coordinate
print("\(place)")
manager.stopUpdatingLocation()
}
func locationManager(manager: CLLocationManager, didFailWithError error: NSError) {
print("InterfaceController: \(NSDate()) - locationManager didFailWithError")
print("CL failed: \(error)")
}
}

Related

Trying to incorporate sound within a beacon region in Swift

I am getting "Use of unresolved identifier 'player' in my code using beacons and regions. For this particular region, I also want it to play a sound (Siren.wav). Code is below:
import Combine
import CoreLocation
import SwiftUI
import AVFoundation
class BeaconDetector: NSObject, ObservableObject, CLLocationManagerDelegate {
var objectWillChange = ObservableObjectPublisher()
var locationManager: CLLocationManager?
var lastDistance = CLProximity.unknown
var player: AVAudioPlayer?
// var audioPlayer = AVAudioPlayer()
override init() {
super.init()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestWhenInUseAuthorization()
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedWhenInUse {
if CLLocationManager.isMonitoringAvailable(for: CLBeaconRegion.self) {
if CLLocationManager.isRangingAvailable() {
startScanning()
}
}
}
}
func startScanning() {
let uuid = UUID(uuidString: "00000000-0000-0000-0000-000000000000")!
let constraint = CLBeaconIdentityConstraint(uuid: uuid)
let beaconRegion = CLBeaconRegion(beaconIdentityConstraint: constraint, identifier: "MyBeacon")
locationManager?.startMonitoring(for: beaconRegion)
locationManager?.startRangingBeacons(satisfying: constraint)
}
func locationManager(_ manager: CLLocationManager, didRange beacons: [CLBeacon], satisfying beaconConstraint: CLBeaconIdentityConstraint) {
if let beacon = beacons.first {
update(distance: beacon.proximity)
} else {
update(distance: .unknown)
}
}
func update(distance: CLProximity) {
lastDistance = distance
self.objectWillChange.send()
}
}
struct BigText: ViewModifier {
func body(content: Content) -> some View {
content
.font(Font.system(size: 72, design: .rounded))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
}
}
struct ContentView: View {
#ObservedObject var detector = BeaconDetector()
var body: some View {
if detector.lastDistance == .immediate {
return Text("DANGER TOO CLOSE")
.modifier(BigText())
.background(Color.red)
.edgesIgnoringSafeArea(.all)
func playSound() {
guard let url = Bundle.main.url(forResource: "Siren", withExtension: "wav") else { return }
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue)
guard let player = player else { return }
player.play()
}
catch let error {
print(error.localizedDescription)
The reason you get an "unresolved identifier" error is because the variable player is not defined in the playSound() method. In the Swift language, each variable declaration has a specific "scope" and they cannot be accessed outside that scope.
In this case, player is defined as a member variable in the BeaconDetector class. Because the playSound() method is not in the same variable "scope", you get that error when you try to access the variable.
You might want to read this tutorial on how variable scope works in Swift.

How do I save data using NSKeyedArchiver?

I am very close to finishing my first iOS App using Swift 4 and iOS 11.
The app has a list displayed in a table view controller and a detail view with a UITextView object that is editable. My goal is for the user to be able to make edits to the content in the UITextView and save those changes using NSKeyedArchiver.
I have the list view complete and the detail view connected. You can make edits but they do not save.
I have confirmed that the entry does save to memory that persists beyond the session, but the edits do not save.
Reviews of documentation and working through multiple tutorials have not provided the insights needed. I have attached a screen shot to show the interface of the detail view and here is the code from the detail view controller where the save button triggers the Save action:
import UIKit
import os.log
class ViewController: UIViewController, UINavigationControllerDelegate, UITextViewDelegate {
var season: Season?
//MARK: Properties
#IBOutlet weak var seasonDetail: UITextView!
#IBAction func saveButton(_ sender: UIBarButtonItem) {
if let selectedDetail = seasonDetail.text {
seasonDetail.text = selectedDetail
} else {
print("failed to save changes.")
}
saveChanges()
print("Save button clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
title = season?.name
seasonDetail.text = season?.detail
seasonDetail.delegate=self
}
override func viewWillDisappear(_ animated: Bool) {
season?.detail = (seasonDetail?.text)!
}
func textViewDidEndEditing(_ textView: UITextView) {
seasonDetail.text = season?.detail
}
//MARK: UITextViewdDelegate
func textViewShouldReturn(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
func saveChanges() {
print("Saving items to: \(Season.ArchiveURL)")
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(season as Any, toFile: Season.ArchiveURL.path)
if isSuccessfulSave {
os_log("Season sucessfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save season.", log: OSLog.default, type: .debug)
}
}
}
Here is the code from the data model class:
import UIKit
import os.log
class Season: NSObject, NSCoding {
//MARK: Properties
var name: String
var detail: String
//MARK: Archiving Paths
static let DocumentsDirectory = FileManager().urls(for: .documentDirectory, in: .userDomainMask).first!
static let ArchiveURL = DocumentsDirectory.appendingPathComponent("season")
//MARK: Types
struct PropertyKey {
static let name = "name"
static let detail = "detail"
}
//MARK: Initialization
init?(name: String, detail: String) {
guard !name.isEmpty else {
return nil
}
guard !detail.isEmpty else {
return nil
}
// Initialize stored properties
self.name = name
self.detail = detail
}
//MARK: NSCoding
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: PropertyKey.name)
aCoder.encode(detail, forKey: PropertyKey.detail)
}
required convenience init?(coder aDecoder: NSCoder) {
// the name is required. If we cannnot get a name string, the initializer should fail.
guard let name = aDecoder.decodeObject(forKey: PropertyKey.name) as? String
else {
os_log("Unable to decode the name for a Season object.", log: OSLog.default, type: .debug)
return nil
}
let detail = aDecoder.decodeObject(forKey: PropertyKey.detail)
self.init(name: name, detail: detail as! String)
}
}
My goal is to understand what is missing with my code and know how to persist all the data, including the edits. I would appreciate any direction that would help.
Please check :
class ViewController: UIViewController, UINavigationControllerDelegate, UITextViewDelegate {
var season: Season?
#IBOutlet weak var seasonDetail: UITextView!
#IBAction func saveButton(_ sender: UIBarButtonItem) {
if let selectedDetail = seasonDetail.text {
season?.detail = selectedDetail // this is the line
} else {
print("failed to save changes.")
}
saveChanges()
print("Save button clicked")
}
override func viewDidLoad() {
super.viewDidLoad()
if season == nil {
season = Season(name: "Season Name", detail: "Season Details")
}
title = season?.name
seasonDetail.text = season?.detail
seasonDetail.delegate=self
}
override func viewWillDisappear(_ animated: Bool) {
season?.detail = (seasonDetail?.text)!
}
func textViewDidEndEditing(_ textView: UITextView) {
season?.detail = seasonDetail.text
}
//MARK: UITextViewdDelegate
func textViewShouldReturn(_ textView: UITextView) -> Bool {
textView.resignFirstResponder()
return true
}
func saveChanges() {
print("Saving items to: \(Season.ArchiveURL)")
let isSuccessfulSave = NSKeyedArchiver.archiveRootObject(season as Any, toFile: Season.ArchiveURL.path)
if isSuccessfulSave {
os_log("Season sucessfully saved.", log: OSLog.default, type: .debug)
} else {
os_log("Failed to save season.", log: OSLog.default, type: .debug)
}
}
}

Is NSInMemoryStoreType Incompatible with NSBatchDeleteRequest?

I am currently unit testing a layer that interacts with Core Data. It saves, deletes, and updates an Item object. However, my test that attempts to save a couple of Items and then perform a batch deletion keeps failing.
This is Item:
extension Item {
// MARK: - Properties
#NSManaged public var date: NSDate
#NSManaged public var isTaxable: Bool
#NSManaged public var name: String
#NSManaged public var price: NSDecimalNumber
#NSManaged public var quantity: Double
// MARK: - Fetch Requests
#nonobjc public class func fetchRequest() -> NSFetchRequest<Item> { return NSFetchRequest<Item>(entityName: "Item") }
// MARK: - Validation
// Manual validation for `Decimal` values is needed. A radar is still open, which is located at https://openradar.appspot.com/13677527.
public override func validateValue(_ value: AutoreleasingUnsafeMutablePointer<AnyObject?>, forKey key: String) throws {
if key == "price", let decimal = value.pointee as? Decimal { if decimal < Decimal(0.01) { throw NSError(domain: NSCocoaErrorDomain, code: 1620, userInfo: ["Item": self]) } }
if key == "quantity", let double = value.pointee as? Double { if double == 0 { throw NSError(domain: NSCocoaErrorDomain, code: 1620, userInfo: ["Item": self]) } }
}
}
This is the object that interacts with Core Data, CoreDataStack:
internal class CoreDataStack {
// MARK: - Properties
private let modelName: String
internal lazy var storeContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: self.modelName)
container.loadPersistentStores { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }
return container
}()
internal lazy var managedContext: NSManagedObjectContext = { return self.storeContainer.viewContext }()
// MARK: - Initialization
internal init(modelName: String = "Cart") { self.modelName = modelName }
// MARK: - Saving
internal func saveContext() throws {
guard managedContext.hasChanges else { return }
do { try managedContext.save() } catch let error as NSError { throw error }
}
}
This is the object that manages persistence with Core Data:
internal final class ItemPersistenceService {
// MARK: - Properties
private let coreDataStack: CoreDataStack
// MARK: - Initialization
internal init(coreDataStack: CoreDataStack) {
self.coreDataStack = coreDataStack
print("init(coreDataStack:) - ItemPersistenceService")
}
// MARK: - Saving
#discardableResult internal func saveItem(withInformation information: ItemInformation) throws -> Item {
let item = Item(context: coreDataStack.managedContext)
item.name = information.name
item.quantity = information.quantity
item.price = information.price as NSDecimalNumber
item.date = information.date as NSDate
item.isTaxable = information.isTaxable
do {
try coreDataStack.saveContext()
} catch let error as NSError {
throw error
}
return item
}
// MARK: - Deleting
internal func delete(item: Item) throws {
coreDataStack.managedContext.delete(item)
do {
try coreDataStack.saveContext()
} catch let error as NSError {
throw error
}
}
internal func deleteAllItems() throws {
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: Item.description())
let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
do {
try coreDataStack.managedContext.persistentStoreCoordinator?.execute(deleteRequest, with: coreDataStack.managedContext)
} catch let error as NSError {
throw error
}
}
// MARK: - Fetching
internal func itemsCount() throws -> Int {
let fetchRequest = NSFetchRequest<NSNumber>(entityName: Item.description())
fetchRequest.resultType = .countResultType
do {
let result = try coreDataStack.managedContext.fetch(fetchRequest)
guard let count = result.first?.intValue else { fatalError("Invalid result") }
return count
} catch {
throw error
}
}
}
This is the CoreDataStack subclass that I use for testing, which contains an in-memory store:
internal final class TestCoreDataStack: CoreDataStack {
// MARK: - Initialization
internal override init(modelName: String = "Cart") {
super.init(modelName: modelName)
let persistentStoreDescription = NSPersistentStoreDescription()
persistentStoreDescription.type = NSInMemoryStoreType
let container = NSPersistentContainer(name: modelName)
container.persistentStoreDescriptions = [persistentStoreDescription]
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") }
self.storeContainer = container
}
}
}
Finally, this is the test that keeps failing:
internal func test_ItemPersistenceService_Delete_All_Managed_Object_Context_Saved() {
do {
try service.saveItem(withInformation: information)
try service.saveItem(withInformation: information)
} catch { XCTFail("Expected `Item`") }
expectation(forNotification: .NSManagedObjectContextDidSave, object: coreDataStack.managedContext) { (notification) in return true }
do { try service.deleteAllItems() } catch { XCTFail("Expected deletion") }
waitForExpectations(timeout: 2.0) { error in XCTAssertNil(error, "Expected save to occur") }
}
Questions
Is NSInMemoryStoreType incompatible with NSBatchDeleteRequest?
If not, then what am I doing incorrectly that is causing my test to fail repeatedly?
You can always create a persistent store of type SQLite and store it at /dev/null. Here's the code to do it on a swift XCTest class:
var container: NSPersistentContainer!
override func setUp() {
super.setUp()
container = NSPersistentContainer(name: "ModelName")
container.persistentStoreDescriptions[0].url = URL(fileURLWithPath: "/dev/null")
container.loadPersistentStores { (description, error) in
XCTAssertNil(error)
}
}
I was hoping to use the same method for deleting a large number of objects efficiently too, but this page states that the NSBatchDeleteRequest is only compatible with SQLite persistent store types, In-memory store types are not supported.
https://developer.apple.com/library/content/featuredarticles/CoreData_Batch_Guide/BatchDeletes/BatchDeletes.html
Important: Batch deletes are only available when you are using a
SQLite persistent store
The different persistent store types are listed here:
https://developer.apple.com/documentation/coredata/nspersistentstorecoordinator/persistent_store_types

Core Data in Swift

Trying to work with Core Data in Swift. Found this one example:
http://www.sep.com/sep-blog/2014/06/23/a-c-developer-learns-swift-part-1-core-data/
Created Entity "Person" with two string fields - lastname and firstname. Created UITableViewController (MainTableViewController) to display records on the screen. Created UIViewController (DetailViewController) to add new records. Created my own class (AddrBook) for entity data.
Does not work display the records contained in the entity in main class - MainTableViewController.
My class AddrBook.swift:
import UIKit
import CoreData
#objc(AddrBook)
class AddrBook: NSManagedObject {
#NSManaged var lastname:String
#NSManaged var firstname:String
}
UIViewController to add new records. DetailViewController.swift:
import UIKit
import CoreData
class DetailViewController: UIViewController {
#IBOutlet var lastNameField : UITextField = nil
#IBOutlet var firstNameField : UITextField = nil
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func saveButtonPressed(sender : AnyObject) {
let appDelegate:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDelegate.managedObjectContext
let projectEntity = NSEntityDescription.entityForName("Person", inManagedObjectContext: context)
var newPerson = AddrBook(entity: projectEntity, insertIntoManagedObjectContext: context)
newPerson.lastname = lastNameField.text
newPerson.firstname = firstNameField.text
context.save(nil)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
Theoretically, in this class all goes well. Entry must be added.
The main class MainTableViewController.swift. To display the records. Trying to get them through the NSLog:
import UIKit
import CoreData
class MainTableViewController: UITableViewController {
init(style: UITableViewStyle) {
super.init(style: style)
}
init(coder aDecoder: NSCoder!) {
super.init(coder: aDecoder)
}
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest(entityName: "Person")
request.returnsObjectsAsFaults = false
let appDelegate:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDelegate.managedObjectContext
var results:NSArray = context.executeFetchRequest(request, error: nil)
for currentPerson in results as AddrBook[] {
NSLog("\(currentPerson.lastname)")
NSLog("\(currentPerson.firstname)")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
override func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return 0
}
}
Shows that there is an error in the expression
for currentPerson in results as AddrBook[] {
Error:
Cannot convert the expression's type 'AddrBook[]' to type 'AddrBook[]'
What am I doing wrong?
for LombaX:
override func viewDidLoad() {
super.viewDidLoad()
let request = NSFetchRequest(entityName: "Person")
request.returnsObjectsAsFaults = false
let appDelegate:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
let context:NSManagedObjectContext = appDelegate.managedObjectContext
var results : AddrBook[]? = context.executeFetchRequest(request, error: nil) as? AddrBook[]
NSLog("\(results)")
if let array = results // check for nil and unwrap
{
for currentPerson in array as AddrBook[] {
NSLog("\(currentPerson.lastname)")
NSLog("\(currentPerson.firstname)")
}
}
// var results:NSArray = context.executeFetchRequest(request, error: nil)
/*for currentPerson in results as AddrBook[] {
NSLog("\(currentPerson.lastname)")
NSLog("\(currentPerson.firstname)")
}*/
}
Output NSLog - 2014-06-24 21:25:41.243 lesson-12-swift[1651:136375] nil
Variable results is nil :-(
Link to project in Dropbox: https://www.dropbox.com/s/q42rw5fw470timi/lesson-12-swift.zip
First, check that you filled the class in the data model:
As ProjectName.AddrBook (for swift classes you have to specify even the project name). (NOTE: this is needed only if you haven't used the prefix #objc(AddrBook) before the class, but I see that you used it, so this is not the problem).
or
as AddrBook as in this image in the Class section, top right
Moreover, change your cast like these:
// since executeFetchRequest can return nil, cast it as an optional array of [AddrBook]
// note: the first [AddrBook]? Can be omitted
var results : [AddrBook]? = context.executeFetchRequest(request, error: nil) as? [AddrBook]
if let array = results // check for nil and unwrap
{
for currentPerson in array as [AddrBook] {
// print
}
}
Or, less explicit and no check for nil
var results = context.executeFetchRequest(request, error: nil)
for currentPerson in results as [AddrBook] {
// print
}
let arrayresult = context!.executeFetchRequest(request, error: &error)
var arrayvalues=NSArray(array: arrayresult!)
for obj in arrayvalues as [AddrBook]
{
}

Type 'className -> () -> className!' does not conform to protocol

I'm messing around with Swift. I have a protocol defined as
protocol timerProtocol {
func timerFired()
}
A class who holds a reference to the delegate
class Stopwatch: NSObject {
var delegate: protocol <timerProtocol>
init(delegate: protocol <timerProtocol> ) {
self.delegate = delegate
}
...
}
and a class that implements the protocol
class StopwatchesTableViewController: UITableViewController, timerProtocol {
func timerFired() {
println("timer");
}
let stopwatch = Stopwatch(delegate: self) // Error here
...
}
I get the error when declaring the stopwatch - "Type 'StopwatchesTableViewController -> () -> StopwatchesTableViewController!' does not conform to protocol 'timerProtocol'"
How do I fix this issue?
Change var delegate: protocol <timerProtocol>
To var delegate: timerProtocol?
syntactically and logically that works for me like a charm:
protocol TimerProtocol {
func timerFired()
}
class Stopwatch {
var delegate: protocol <TimerProtocol>? = nil
init() { }
convenience init(delegate: protocol <TimerProtocol> ) {
self.init()
self.delegate = delegate
}
}
class StopwatchesTableViewController: UITableViewController, TimerProtocol {
#lazy var stopwatch: Stopwatch = Stopwatch()
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
stopwatch.delegate = self
}
func timerFired() {
println("timer");
}
}
NOTE: the protocols' names should start with capital letter.
or
the StopwatchesTableViewController class would look like e.g. this:
class StopwatchesTableViewController: UITableViewController, TimerProtocol {
var stopwatch: Stopwatch? = nil
init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
stopwatch = Stopwatch(delegate: self)
}
func timerFired() {
println("timer");
}
}
Try change,
let stopwatch = Stopwatch(delegate: self) // Error here
to
#lazy var stopwatch: Stopwatch = Stopwatch(delegate: self)
Your code
let stopwatch = Stopwatch(delegate: self)
is in the scope of the class (not inside a func) and hence self refers to the class (not an instance). The class does not conform to the protocol, only its instances.
You need to do
let stopwatch: Stopwatch
func init() {
stopwatch = Stopwatch(delegate: self)
}

Resources