I'm trying to build an iPhone app with swift, I've installed all the dependencies on "connect to your back-end" https://docs.aws.amazon.com/aws-mobile/latest/developerguide/add-aws-mobile-nosql-database.html , I even have the amazon aws login page so it's def connected, just whenenver I add this line:
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
that it tells me to do in the tutorial, the app crashes on the screen that code is associated to with Thread 1: SIGABRT. Here's my code on the screen it's on.
import UIKit
import MapKit
import AWSCore
import AWSAuthUI
import AWSMobileClient
import AWSUserPoolsSignIn
import AWSDynamoDB
class SampleViewController: UIViewController, CLLocationManagerDelegate {
let locationManager = CLLocationManager()
override func viewDidLoad() {
super.viewDidLoad()
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
if !AWSSignInManager.sharedInstance().isLoggedIn {
presentAuthUIViewController()
self.navigationController?.isNavigationBarHidden = true
}
}
#IBAction func askPermission(_ sender: UIButton) {
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestWhenInUseAuthorization()
locationManager.startUpdatingLocation()
}
func presentAuthUIViewController() {
let config = AWSAuthUIConfiguration()
config.enableUserPoolsUI = true
AWSAuthUIViewController.presentViewController(
with: self.navigationController!,
configuration: config, completionHandler: { (provider: AWSSignInProvider, error: Error?) in
if error == nil {
// SignIn succeeded.
} else {
// end user faced error while loggin in, take any required action here.
}
})
}
}
I'd appreciate any help, and can post more information if needed. Thank you so much.
Can you add the following to the awsconfiguration.json after the section that defines "DynamoDBObjectMapper". Replace the 'YourRegion' placeholder below with the region where your Database is located. This is a required section in the awsconfiguration.json file and there may be in a bug in the tool that generates the config.
"DynamoDB": {
"Default": {
"Region": "YourRegion"
}
},
Related
Im having a trouble to add locationManager.requestWhenInUseAuthorization()
What i expect : popup asking for location permission to user
What heppend : nothing show
Setting : location setting in iphone : never
Plist:
LocationAlwaysAndWhenInuseUsageDescription
LocationAlwaysUsageDescription
LocationWhenInUseUsageDescription
import Foundation
import UIKit
import CoreLocation
class NoobNoobVC: BaseVC, CLLocationManagerDelegate {
var locationManager: CLLocationManager!
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
locationManager.distanceFilter = 5
locationManager.requestWhenInUseAuthorization()
if CLLocationManager.locationServicesEnabled() {
//locationManager.startUpdatingHeading()
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
locationManager.requestWhenInUseAuthorization()
}
}
Documentation says:
You may call requestWhenInUseAuthorization() whenever the current authorization status is not determined (CLAuthorizationStatus.notDetermined).
&
If the initial authorization status is anything other than CLAuthorizationStatus.notDetermined, this method does nothing and doesn't call the locationManager(_:didChangeAuthorization:) method.
You have to use authorizationStatus() to check the authorization status and then take a proper action like - inform the user what's wrong, ... and what can be done about it. You can call requestWhenInUseAuthorization() only if it's .notDetermined. This class function is marked as deprecated and if you're targeting iOS >= 14.0 you should use the instance variant.
Now I'm working on iOS using RxSwift framework. In my app I have to user user location, but I don't need it to be updated in real time. It's enough if location updated every time user opens app or does some defined action. Therefore, how about implementing singleton class where the last result is cached. Each update by action changes cached result and accepts it to the stream. Stream's default value is cached value. Then, views where user location is needed would subscribe on this stream.
Example implementation using Cache and RxSwift
import Foundation
import Cache
import CoreLocation
import RxSwift
import RxCocoa
class UserLocationManager: NSObject {
private enum Keys: String {
case diskConfig = "Disk Config"
case lastLocation = "User Last Location"
}
// MARK: - Variables
private func cache<T: Codable>(model: T.Type) -> Cache.Storage<T> {
let storage = try! Cache.Storage(diskConfig: DiskConfig(name: Keys.diskConfig.rawValue), memoryConfig: MemoryConfig(expiry: .never), transformer: TransformerFactory.forCodable(ofType: model))
return storage
}
private let locationManager = CLLocationManager()
private var lastPosition: MapLocation? {
get {
do {
return try cache(model: MapLocation.self).object(forKey: Keys.lastLocation.rawValue)
}
catch { return nil }
}
set {
do {
guard let location = newValue else { return }
try cache(model: MapLocation.self).setObject(location, forKey: Keys.lastLocation.rawValue)
}
catch { }
}
}
private let disposeBag = DisposeBag()
static let shared = UserLocationManager()
var locationStream = BehaviorRelay<CLLocationCoordinate2D?>(value: nil)
// MARK: - Methods
func updateLocation() {
if CLLocationManager.locationServicesEnabled() {
locationManager.requestLocation()
}
}
func subscribe() {
locationStream.accept(lastPosition?.clCoordinate2D)
locationStream.subscribe(onNext: { [weak self] location in
guard let `self` = self else { return }
guard let location = location else { return }
self.lastPosition = MapLocation(latitude: location.latitude, longitude: location.longitude)
}).disposed(by: disposeBag)
locationManager.delegate = self
}
// MARK: - Lifecycle
override init() {
super.init()
defer {
self.subscribe()
}
}
}
// MARK: - CLLocationManagerDelegate
extension UserLocationManager: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let location = locations.first else { return }
UserLocationManager.shared.locationStream.accept(location.coordinate)
}
func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
}
}
There's no problem conceptually of having a global stream that can be subscribed to. However, your specific implementation is worrisome to me.
One of the cool things about Observable streams is that they are lazy, no work is done unless needed, but you are writing extra code to bypass that feature and I don't think it's necessary. Also, storing there current location when the app goes into the background and just assuming that is a valid location when the app comes back to the foreground (possibly weeks later) sounds inappropriate to me.
The RxCocoa package already has an Rx wrapper for CLLocationManager. It seems to me it would be far simpler to just use it. If you only need one location update then use .take(1). I'd be inclined to add a filter on the accuracy of the location before the take(1).
I have data stored in a dynamoDB table on Amazon. I'm able to query that table. Within the dynamoDbObjectMapper.query statement I can print the result. With the result I want to create GMSMakers (pins on a Google Map). But when I call this the app crashes with a message stating "The API method must be called from the main thread". I just don't seem to get what I need to do.
So my question is if somebody can provide an example that can help me understand what's going on and guide me towards the solution. Passing the result of query "upwards" to the main thread so I can call the Google SDK functions.
I would appreciate it very much.....
Here is my code (please be gentle....). Result : the GMSMarker call generates this runtime exception: 'GMSThreadException', reason: 'The API method must be called from the main thread'
import UIKit
import GoogleMaps
import AWSCore
import AWSMobileClient
import AWSDynamoDB
class DiscoverViewController: UIViewController, CLLocationManagerDelegate, GMSMapViewDelegate {
#IBOutlet weak var mapView: GMSMapView!
var currentPositionMarker: GMSMarker!
var locationManager = CLLocationManager()
var output: AWSDynamoDBPaginatedOutput!
var paginatedOutput: AWSDynamoDBPaginatedOutput?
override func viewDidLoad() {
super.viewDidLoad()
//Hide the mapview until the currentlocation is found. unhide will be done from the event handler.
mapView.isHidden = true
mapView.delegate = self
self.locationManager.requestAlwaysAuthorization()
// For use in foreground
self.locationManager.requestWhenInUseAuthorization()
// Request current location: once.
if CLLocationManager.locationServicesEnabled() {
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
locationManager.requestAlwaysAuthorization()
locationManager.requestLocation() //One time request
}
//first create query expression
let queryExpression = AWSDynamoDBQueryExpression()
queryExpression.keyConditionExpression = "#attribute1 = :value1"
queryExpression.expressionAttributeNames = [ "#attribute1": "storyUserId" ]
queryExpression.expressionAttributeValues = [ ":value1": "roger"]
let dynamoDbObjectMapper = AWSDynamoDBObjectMapper.default()
dynamoDbObjectMapper.query(Stories.self, expression: queryExpression) { (output: AWSDynamoDBPaginatedOutput?, error: Error?) in
if error != nil {
print("The request failed. Error: \(String(describing: error))")
}
if output != nil {
print(output!.items)
for stories in output!.items {
let story = stories as? Stories
print("Pin read \(story!._storyUserId!)")
print("Pin read \(story!._storyName!)")
print("Pin read \(story!._storyDateTime!)")
print("Pin read \(story!._storyAudioFilename!)")
print("Pin read \(story!._storyLongitude!)")
print("Pin read \(story!._storyLatitude!)")
print("Creating pin")
let positionMarker = GMSMarker()
positionMarker.position.latitude = story!._storyLatitude as! Double
positionMarker.position.longitude = story!._storyLongitude as! Double
positionMarker.title = story!._storyName
positionMarker.snippet = story!._storyAudioFilename
positionMarker.iconView = currentPositionMarkerView
positionMarker.map = self.mapView
self.showPins(result: output!)
} else {
print("No data read from the table")
}
}
// Do any additional setup after loading the view, typically from a nib.
}
I'm trying to create a fake authenticator for my unit tests that can manually set the user as logged in or logged out and bypass the API my code actually uses where I'd need a real accessToken to log the user in.
I've wrapped the Authentication API my app uses in to the following class:
API Wrapper
import OIDC
protocol Authenticator {
func getValidAccessToken(completionHandler: #escaping (Error?, String?) -> Void)
}
struct OIDCAuthenticator: Authenticator {
func getValidAccessToken(completionHandler: #escaping (Error?, String?) -> Void) {
//API call
OIDCHelper.getValidAccessToken { (error, accessToken) in
DispatchQueue.main.async {
completionHandler( error, accessToken)
}
}
}
}
Then I create a Fake Authenticator using the same protocol for testing purposes
Fake/Testing Authenticator
import OIDC
import Foundation
///Mocks the user being logged in our logged out for Testing purposes
struct FakeAuthenticator: Authenticator {
let error: OIDCError?
let accessToken: String?
func getValidAccessToken(completionHandler: #escaping (Error?, String?) -> Void) {
completionHandler(error, accessToken)
}
init(loggedIn: Bool) {
if loggedIn {
error = .tokenNotFoundInKeychainData
accessToken = nil
} else {
error = nil
accessToken = "abcdefg"
}
}
}
Settings the OIDCAuthenticator API Wrapper works fine when settings the authenticator in the ViewController subclass.
TableViewController Implementation
import UIKit
import OIDC
class SettingsPageTableViewController: UITableViewController{
// MARK: - Outlets and variables
var authenticator: Authenticator!
private var isUserLoggedIn = false
// MARK: - LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
authenticator = OIDCAuthenticator()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
authenticator.getValidAccessToken { [weak self] (error, _) in
self?.isUserLoggedIn = (error == nil)
self?.setLogginStatus()
}
}
}
However when I try to do the same thing with the FakeAuthenticator in my Unit tests I get the following error:
SettingsViewController Test Class
import XCTest
import UIKit
#testable import MyProject
class SettingsViewControllerTests: XCTestCase {
var viewController: SettingsPageTableViewController!
override func setUp() {
super.setUp()
configureViewControllerForTesting()
}
private func configureViewControllerForTesting() {
let storyboard = UIStoryboard(name: "SettingsPage", bundle: nil)
let navigationController = storyboard.instantiateInitialViewController() as! UINavigationController
viewController = navigationController.topViewController as! SettingsPageTableViewController
_ = viewController.view
}
func testSignInButtonIsAvailableWhenUnauthenticated() {
viewController.authenticator = FakeAuthenticator(loggedIn: false)
}
}
The same things happens when I swap out FakeAuthenticator with OIDCAuthenticator. I've also attempted to cast the FakeAuthenticator to Authenticator but this merely alters the Error to Cannot assign value of type 'Authenticator' to type 'Authenticator!'.
Why am I getting this error and what is the best approach to fixing this?
You need to remove the files from your test target since you're already importing your whole project with #testable.
Between the pod spec and what is currently on S.O. I had a tough time figuring out how to get speech-to-text working using SpeechKit + CocoaPod + Swift. Finally got it working so figured I'd help the next poor soul that comes looking for help! :)
First install the CocoaPod: https://cocoapods.org/pods/SpeechKit
Add #import <SpeechKit/SpeechKit.h> to your bridging header
Login to Nuance's dev portal and create an app: https://developer.nuance.com/
Clean up the demo code so that is is more organized. I just wanted as much of the code to be in one place as possible so you can see a fully working implementation.
Then create a UIViewController and add the following code with the correct credentials:
import UIKit
import SpeechKit
class SpeechKitDemo: UIViewController, SKTransactionDelegate {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//!!link this to a corresponding button on the UIViewController in I.B.
#IBAction func tappedButton(sender: AnyObject) {
// All fields are required.
// Your credentials can be found in your Nuance Developers portal, under "Manage My Apps".
let SKSAppKey = "[Get this from the nuance app info page]";
let SKSAppId = "[Get this from the nuance app info page]";
let SKSServerHost = "[Get this from the nuance app info page]";
let SKSServerPort = "[Get this from the nuance app info page]";
let SKSLanguage = "eng-USA";
let SKSServerUrl = "nmsps://\(SKSAppId)#\(SKSServerHost):\(SKSServerPort)"
let session = SKSession(URL: NSURL(string: SKSServerUrl), appToken: SKSAppKey)
//this starts a transaction that listens for voice input
let transaction = session.recognizeWithType(SKTransactionSpeechTypeDictation,
detection: .Short,
language: SKSLanguage,
delegate: self)
print(transaction)
}
//required delegate methods
func transactionDidBeginRecording(transaction: SKTransaction!) { }
func transactionDidFinishRecording(transaction: SKTransaction!) { }
func transaction(transaction: SKTransaction!, didReceiveRecognition recognition: SKRecognition!) {
//Take the best result
let topRecognitionText = recognition.text;
print("Best rec test: \(topRecognitionText)")
//Or iterate through the NBest list
let nBest = recognition.details;
for phrase in (nBest as! [SKRecognizedPhrase]!) {
let text = phrase.text;
let confidence = phrase.confidence;
print("\(confidence): \(text)")
}
}
func transaction(transaction: SKTransaction!, didReceiveServiceResponse response: [NSObject : AnyObject]!) { }
func transaction(transaction: SKTransaction!, didFinishWithSuggestion suggestion: String!) { }
func transaction(transaction: SKTransaction!, didFailWithError error: NSError!, suggestion: String!) { }
}