Swift: HEREMaps geocoding sharedSdkEngineNotInstantiated error being thrown - swift

So I'm using the HERE iOS SDK. For some reason, the try block isn't working, I keep getting the error printing out that is 'sharedSdkEngineNotInstantiated'. The getCoordinates function is what I'm trying to test after instantiation--I included it so that you guys aren't left guessing what the end goal is. Thanks for any and all help!
class functions: NSObject, ObservableObject {
var searchEngine: SearchEngine?
override init() {
do {
try searchEngine = SearchEngine()
} catch let engineInstantiationError {
print("Failed to make the search engine. The cause was \(engineInstantiationError)")
}
}
func getCoordinates5(from address: String) {
guard searchEngine != nil else {
return
}
let searchOptions = SearchOptions(maxItems: 10)
let query = TextQuery(address, near: GeoCoordinates(latitude: 40.7128, longitude: 74.0060))
_ = searchEngine!.search(textQuery: query, options: searchOptions) { (searchError, searchResultItems) in
guard searchResultItems != nil else {
return
}
for index in 0..<searchResultItems!.count {
let number = index + 1
print("Location \(number): \(searchResultItems![index])")
}
}
}
}

As explained here, you should instantiate the sdk manually since probably when your code is executed you don't have any map loaded yet (showing the map will automatically instantiate the sdk). So all you need is
// We must explicitly initialize the HERE SDK if no MapView is present.
do {
try SDKInitializer.initializeIfNecessary()
} catch {
fatalError("Failed to initialize HERE SDK")
}
before instantiating the SearchEngine

Related

Is there a battery level did change notification equivalent for kIOPSCurrentCapacityKey on macOS?

I am building a Swift app that monitors the battery percentage, as well as the charging state, of a Mac laptop's battery. On iOS, there is a batteryLevelDidChange notification that is sent when the device's battery percentage changes, as well as a batteryStateDidChange notification that is sent when the device is plugged in, unplugged, and fully charged.
What is the macOS equivalent of those two notifications in Swift, or more specifically, for kIOPSCurrentCapacityKey and kIOPSIsChargingKey? I read through the notification documentation and didn't see any notifications for either. Here is the code I have for fetching the current battery charge level and charging status:
import Cocoa
import IOKit.ps
class MainViewController: NSViewController {
enum BatteryError: Error { case error }
func getMacBatteryPercent() {
do {
guard let snapshot = IOPSCopyPowerSourcesInfo()?.takeRetainedValue()
else { throw BatteryError.error }
guard let sources: NSArray = IOPSCopyPowerSourcesList(snapshot)?.takeRetainedValue()
else { throw BatteryError.error }
for powerSource in sources {
guard let info: NSDictionary = IOPSGetPowerSourceDescription(snapshot, ps as CFTypeRef)?.takeUnretainedValue()
else { throw BatteryError.error }
if let name = info[kIOPSNameKey] as? String,
let state = info[kIOPSIsChargingKey] as? Bool,
let capacity = info[kIOPSCurrentCapacityKey] as? Int,
let max = info[kIOPSMaxCapacityKey] as? Int {
print("\(name): \(capacity) of \(max), \(state)")
}
}
} catch {
print("Unable to get mac battery percent.")
}
}
override func viewDidLoad() {
super.viewDidLoad()
getMacBatteryPercent()
}
}
(I'm replying to this almost 3-year-old question as it is the third result that comes up on the Google search "swift iokit notification".)
The functions you're looking for are IOPSNotificationCreateRunLoopSource and IOPSCreateLimitedPowerNotification.
Simplest usage of IOPSNotificationCreateRunLoopSource:
import IOKit
let loop = IOPSNotificationCreateRunLoopSource({ _ in
// Perform usual battery status fetching
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), loop, .defaultMode)
Note that the second parameter context is passed as the only parameter in the callback function, which can be used to pass the instance as a pointer to the closure since C functions do not capture context. (See the link below for actual implementation.)
Here is my code that converts the C-style API into a more Swift-friendly one using the observer pattern: (don't know how much performance benefit it will has for removing run loops)
import Cocoa
import IOKit
// Swift doesn't support nested protocol(?!)
protocol BatteryInfoObserverProtocol: AnyObject {
func batteryInfo(didChange info: BatteryInfo)
}
class BatteryInfo {
typealias ObserverProtocol = BatteryInfoObserverProtocol
struct Observation {
weak var observer: ObserverProtocol?
}
static let shared = BatteryInfo()
private init() {}
private var notificationSource: CFRunLoopSource?
var observers = [ObjectIdentifier: Observation]()
private func startNotificationSource() {
if notificationSource != nil {
stopNotificationSource()
}
notificationSource = IOPSNotificationCreateRunLoopSource({ _ in
BatteryInfo.shared.observers.forEach { (_, value) in
value.observer?.batteryInfo(didChange: BatteryInfo.shared)
}
}, nil).takeRetainedValue() as CFRunLoopSource
CFRunLoopAddSource(CFRunLoopGetCurrent(), notificationSource, .defaultMode)
}
private func stopNotificationSource() {
guard let loop = notificationSource else { return }
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), loop, .defaultMode)
}
func addObserver(_ observer: ObserverProtocol) {
if observers.count == 0 {
startNotificationSource()
}
observers[ObjectIdentifier(observer)] = Observation(observer: observer)
}
func removeObserver(_ observer: ObserverProtocol) {
observers.removeValue(forKey: ObjectIdentifier(observer))
if observers.count == 0 {
stopNotificationSource()
}
}
// Functions for retrieving different properties in the battery description...
}
Usage:
class MyBatteryObserver: BatteryInfo.ObserverProtocol {
init() {
BatteryInfo.shared.addObserver(self)
}
deinit {
BatteryInfo.shared.removeObserver(self)
}
func batteryInfo(didChange info: BatteryInfo) {
print("Changed")
}
}
Credits to this post and Koen.'s answer.
I'd Use this link to get the percentage (looks cleaner)
Fetch the battery status of my MacBook with Swift
And to find changes in the state, use a timer to re-declare your battery state every 5 seconds and then set it as a new variable var OldBattery:Int re-declare it once again and set it as NewBattery, then, write this code:
if (OldBattery =! NewBattery) {
print("battery changed!")
// write the function you want to happen here
}

How to access variable in Document.swift from another class?

In CoreData document I have an entity SpaceLocation which is set of different SpaceLocations. One of them is default / current one, necessary for editing document.
It's defined in main Document.swift as:
#objc var defaultSpaceLocation: SpaceLocation {
return SpaceLocation.defaultSpaceLocation(in: managedObjectContext!)
}
#objc var currentSpaceLocation: SpaceLocation {
get {
if _currentSpaceLocation == nil {
willChangeValue(for: \Document.currentSpaceLocation)
_currentSpaceLocation = defaultSpaceLocation
didChangeValue(for: \Document.currentSpaceLocation)
}
return _currentSpaceLocation!
}
set {
willChangeValue(for: \Document.currentSpaceLocation)
_currentSpaceLocation = newValue
didChangeValue(for: \Document.currentSpaceLocation)
}
}
and in SpaceLocation class as:
class func defaultSpaceLocation(in moc: NSManagedObjectContext) -> SpaceLocation {
let spaceLocationsRequest = NSFetchRequest<SpaceLocation>(entityName: "SpaceLocation")
var result:SpaceLocation? = nil
do {
let spaceLocations = try moc.fetch(spaceLocationsRequest)
result = spaceLocations.filter {$0.isBaseLocation}.first!
}
catch {
Swift.print("•••• ERROR ••••", #file, #function, "Couldn't get Default Location")
}
return result!
}
}
I access to currentSpaceLocation by:
(NSDocumentController.shared.currentDocument as?Document)?.currentSpaceLocation
but it works only if document is in foreground. When app goes background NSDocumentController.shared.currentDocument becomes nil so I cannot access document.currentSpaceLocation. Any idea how to solve this?

Forcing code to wait for object instantiation before continuing

I've written a class, UserLocation (below), which uses CoreLocation to get the user's current place (string) & latitude and longitude (also a string "lat,long" for passing to APIs).
My code below works, but when I initialize the class, the rest of my code doesn't wait for the init to finish before moving on, so I miss the opportunity to assign the location-related values that I'm trying to retrieve.
Is this a sound approach (or should I think about some other more "MVC" organization), and if it is, how can I get my code to wait for the initialization (with location find & reverse geocoding) to finish before moving on. Is there a way to put the code below the initialization into some sort of #escaping closure that's specified in the class's init? I'm new to swift so thanks for your kind advice.
In ViewController.swift's viewDidAppear():
let userLocation = UserLocation() // initializes properly but code below doesn't wait.
locationsArray[0].name = userLocation.place
locationsArray[0].coordinates = userLocation.coordinates
And my UserLocation.swift class:
import Foundation
import CoreLocation
class UserLocation {
var place = ""
var coordinates = ""
let locationManager = CLLocationManager()
var currentLocation: CLLocation!
init() {
returnResults()
}
func returnResults () {
getUserLocation { placemark in
if placemark != nil {
self.place = (placemark?.name)!
self.coordinates = "\((placemark?.location?.coordinate.latitude)!),\((placemark?.location?.coordinate.longitude)!)"
} else {
print("Error retrieving placemark")
}
}
}
func getUserLocation(completion: #escaping (CLPlacemark?) -> ()) {
var placemark: CLPlacemark?
locationManager.requestWhenInUseAuthorization()
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedWhenInUse ||
CLLocationManager.authorizationStatus() == CLAuthorizationStatus.authorizedAlways) {
currentLocation = locationManager.location
let geoCoder = CLGeocoder()
geoCoder.reverseGeocodeLocation(currentLocation) { (placemarks, error) -> Void in
if error != nil {
print("Error getting location: \(error)")
placemark = nil
} else {
placemark = placemarks?.first
}
completion(placemark)
}
}
}
}
extension CLPlacemark {
var cityState: String {
var result = ""
switch (self.locality, self.administrativeArea, self.country) {
case (.some, .some, .some("United States")):
result = "\(locality!), \(administrativeArea!)"
case (.some, _ , .some):
result = "\(locality!), \(country!)"
default:
result = name ?? "Location Unknown"
}
return result
}
}
This is not necessarily a Swift issue. Your problem is caused by the fact that returnResults executes the variables setup in an async manner because it calls an async function - getUserLocation, which is async as reverseGeocodeLocation is async (this is how CoreLocation works - you don't get the location synchronously, but in a callback).
You don't want to wait for returnResults to execute the callback, as this would mean blocking the main thread while CoreLocation initializes and tries to determine the location. Instead you should follow the async pattern by using a completion block that returnResults can use to signal the completion of the location retrieval.
An example for the above would be:
class UserLocation {
var place = ""
var coordinates = ""
let locationManager = CLLocationManager()
var currentLocation: CLLocation!
init() {
// don't call anymore from here, let the clients ask for the locations
}
// This was renamed from returnResults to a more meaningful name
// Using the Bool in the completion to signal the success/failure
// of the location retrieval
func updateLocations(withCompletion completion: #escaping (Bool) -> Void) {
getUserLocation { placemark in
if placemark != nil {
self.place = (placemark?.name)!
self.coordinates = "\((placemark?.location?.coordinate.latitude)!),\((placemark?.location?.coordinate.longitude)!)"
completion(true)
} else {
print("Error retrieving placemark")
completion(false)
}
}
}
...
You can then modify the called code to something like this:
let userLocation = UserLocation()
userLocation.updateLocations { success in
guard success else { return }
locationsArray[0].name = userLocation.place
locationsArray[0].coordinates = userLocation.coordinates
}
You don't block the main thread, and you execute the appropriate code when the location is available.

Cannot convert value of type 'XCUIElement' to expected argument type 'SignUpSetUp'

I have created a helper class SignUpSetUp in order to sign in to my app instead of reusing code. In this class I have a private function waitForElementToAppear to wait for elements in the testing suite to appear. However when using this function it is generating the error:
Cannot convert value of type 'XCUIElement' to expected argument type 'SignUpSetUp'
Why is this and how do I resolve this?
My code is:
import XCTest
class SignUpSetUp: XCTestCase {
var systemAlertMonitorToken: NSObjectProtocol? = nil
static let signUpApp = XCUIApplication()
static let app = XCUIApplication()
class func signUpApp() {
// XCUIApplication().launch()
//signUpSetUp.launch()
sleep(2)
let element = app.buttons["Enable notifications"]
if element.exists {
element.tap()
}
sleep(3)
let notifcationsAlert = self.app.alerts.buttons["OK"]
if notifcationsAlert.exists{
notifcationsAlert.tap()
notifcationsAlert.tap()
}
sleep(2)
waitForElementToAppear(self.app.tabBars.buttons["Nearby"])
let nearbyTab = self.app.tabBars.buttons["Nearby"]
if nearbyTab.exists {
nearbyTab.tap()
}
sleep(2)
let enableLocation = self.app.buttons["Enable location"]
if enableLocation.exists {
enableLocation.tap()
}
let allowLocation = self.app.alerts.buttons["Allow"]
if allowLocation.exists {
allowLocation.tap()
allowLocation.tap()
}
sleep(2)
//waitForElementToAppear(self.app.tabBars.buttons.elementBoundByIndex(4))
let settingsButton = self.app.tabBars.buttons.elementBoundByIndex(4)
XCTAssert(settingsButton.exists)
settingsButton.tap()
let signUpButton = self.app.tables.staticTexts["Sign Up"]
if signUpButton.exists {
signUpButton.tap()
}
}
private func waitForElementToAppear(element: XCUIElement,
file: String = #file, line: UInt = #line) {
let existsPredicate = NSPredicate(format: "exists == true")
expectationForPredicate(existsPredicate,
evaluatedWithObject: element, handler: nil)
waitForExpectationsWithTimeout(5) { (error) -> Void in
if (error != nil) {
let message = "Failed to find \(element) after 5 seconds."
self.recordFailureWithDescription(message,
inFile: file, atLine: line, expected: true)
}
}
}
Your problem is that you are calling an instance method from a class method.
waitForElementToAppear is an instance method, but signUpApp is a class method. In order for your code to work, you need to align the two. Remove class from the signature of signUpApp, and also remove static from your two properties, and change references to self.app to just app.
let signUpApp = XCUIApplication()
let app = XCUIApplication()
func signUpApp() { ... }
Unless you do want the methods to be on a class/static level, in which case you can align in the other direction.
In terms of best practices, there's no need to have two properties holding an instance of XCUIApplication - just have one and use that as they will both function in the same way.

Issue with Swift Closures

I'm having an issue retrieving data from within an closure. I'm calling function called getWallImages which is supposed to return an array. I can print the contents of the array from within the closure, but outside of it the array is empty.
import Foundation
import Parse
class WallPostQuery {
var result = [WallPost]()
func getWallImages() -> [WallPost] {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
}
}
}
//this line prints [] ...empty array?
println(result)
return self.result
}
}
Question
How do I get values out of a closure?
That is because println(result) is executed BEFORE self.results = objects. The closure is executed asynchronously so it executes afterwards. Try making a function that uses results which can be called form the closure:
var result = [WallPost]()
func getWallImages() {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
self.useResults(self.result)
}
}
}
}
func useResults(wallPosts: [WallPost]) {
println(wallPosts)
}
}
Another solution to your problem, so that you can return it from that function is to create your own closure:
var result = [WallPost]()
func getWallImages(completion: (wallPosts: [WallPost]?) -> ()) {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
completion(wallPosts: self.result)
} else {
completion(wallPosts: nil)
}
} else {
completion(wallPosts: nil)
}
}
}
func useResults(wallPosts: [WallPost]) {
println(wallPosts)
}
}
What is happening is that the method is returning before the closure executes.
Fundamentally, you're running into a problem with they way you are managing asynchronous callbacks.
Asynchronous vs synchronous execution, what does it really mean?
You need to create a way of notifying your caller from within your closure. You can achieve this by: requiring your own closure as an input parameters; using a delegate pattern; using a notification.
https://codereview.stackexchange.com/questions/87016/swift-ios-call-back-functions
Each has their benefits/drawbacks, and it depends on your particular situation. The simplest way to get started with async data fetches, is to pass in your own closure. From there, you can jump to another pattern such as the delegate pattern if the need arises.
I think the latter of println(result) is called before because findObjectsInBackgroundWithBlock is executed on background as its name suggests.
So, you can confirm result in the following way,
import Foundation
import Parse
class WallPostQuery {
var result = [WallPost]() {
didSet {
println(result)
}
}
func getWallImages() {
let query = WallPost.query()!
query.findObjectsInBackgroundWithBlock { objects, error in
if error == nil {
if let objects = objects as? [WallPost] {
self.result = objects
//This line will print the three PFObjects I have
println(self.result)
}
}
}
}
}