How do I extract firebase functions to an object in Swift? - swift

I'm new to object oriented programming and am trying to move logic out of my view controller and into its own model so I can reuse endpoint calls and such, but I can't figure out how to alert my view controller that the result has occurred from within the object. Within javascript, I'd normally just set the function to return a value but it is not allowing me to return anything other than Void Cannot convert return expression of type 'Void' to return type 'String'.
import Foundation
import Firebase
class CloudFunctions {
let functions = Functions.functions()
func addNewAccount(param1: String) -> String {
functions.httpsCallable(FunctionsConstants.myFunction).call(["param1Name":param1]) { (result, error) in
if let e = error {
return "\(e)"
} else {
if let result = result {
return "\(result.data)"
} else {
return "Sorry, we couldn't unwrap the result."
}
}
}
}
}
and in my view controller I'm calling it like
let cloudFunctions = CloudFunctions()
self.result = cloudFunctions.addNewAccount(param1: dataHere)
I thought I might be able to create a didSet on result to update the UI, but it won't let the function go through because I get the error: Cannot convert return expression of type 'Void' to return type 'String'

You need a completion
func addNewAccount(param1: String,completion:#escaping(String -> ())){
functions.httpsCallable(FunctionsConstants.myFunction).call(["param1Name":param1]) { (result, error) in
if let e = error {
return "\(e)"
} else {
if let result = result {
completion("\(result.data)")
} else {
completion("Sorry, we couldn't unwrap the result.")
}
}
}
}
addNewAccount(param1:"") { str in
}

Related

Swift - Can I read the value of a enum as an optional without doing a switch?

Does Swift have a syntax similar to case .enumCase(let value) = enum that can be used as a one liner to read the enum as a specific case, or a nil
For instance with a Swift.Result instead of
let error: Error?
if case let .failure(value) = result {
error = value
} else {
error = nil
}
can I write something as
case let .failure(error) = result // Well this I can't
let error = result.as(.failure)
To simplify working with Result (or other enums) you can use next extension:
extension Result {
var value: Success? {
if case let .success(value) = self {
return value
}
return nil
}
var error: Failure? {
if case let .failure(error) = self {
return error
}
return nil
}
}
Then you can get optional values in single line:
let value = result.value
let error = result.error
if let value = result.value {
print(value)
}
if let error = result.error {
print(error)
}

Function that returns an array

I'm trying to get an array to return from a function I call but the return optionArray in the below code give me a "Use of unresolved identifier optionArray.
public func getAdminSites(){
let getSiteData = UserDefaults.standard.object(forKey: "adminSites")
if getSiteData != nil
{
do {
guard let sitesData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(getSiteData as! Data) as? [ModelSites] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
var optionArray = ["All sites"]
for i in 0...sitesData.count-1 {
optionArray.append(sitesData[i].name)
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
return optionArray
}
There are two errors:
Function definition is missing a return type
OptionArray (stored variable) is declared in a control flow if scope and not accessible on a function body level
When you define a function, you can optionally define one or more
named, typed values that the function takes as input, known as
parameters. You can also optionally define a type of value that the
function will pass back as output when it is done, known as its return
type.
source
Fixed code:
public func getAdminSites() -> [String] {
let getSiteData = UserDefaults.standard.object(forKey: "adminSites")
var optionArray = [String]()
if getSiteData != nil
{
do {
guard let sitesData = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(getSiteData as! Data) as? [ModelSites] else {
fatalError("loadWidgetDataArray - Can't get Array")
}
optionArray = ["All sites"]
for i in 0...sitesData.count-1 {
optionArray.append(sitesData[i].name)
}
} catch {
fatalError("loadWidgetDataArray - Can't encode data: \(error)")
}
}
return optionArray
}

Escaping completion handler never happens (Google Places API)

I'm building an app that uses the Google Places API. I currently have a button that, when tapped, gets the address of the current GPS location. The code is in my view controller:
var placesClient: GMSPlacesClient?
#IBAction func buttonTapped(_ sender: AnyObject) {
placesClient?.currentPlace(callback: { (placeLikelihoods, error) -> Void in
guard error == nil else {
print("Current Place error: \(error!.localizedDescription)")
return
}
if let placeLikelihoods = placeLikelihoods {
let place = placeLikelihoods.likelihoods.first?.place
self.addressLabel.text = place?.formattedAddress!.components(separatedBy: ", ").joined(separator: "\n")
}
})
print("Out of the brackets...")
}
When done this way, the function completes and "Out of the brackets..." is printed.
However, when I try moving this code out of the view controller and into a custom class and call it from the view controller, like below, everything within the "placesClient?.currentPlace(callback" block runs (and retrieves the correct address), but "Out of the brackets..." never gets printed and it never returns the value:
class LocationAPIService {
var placesClient: GMSPlacesClient? = GMSPlacesClient.shared()
func getCurrentLocation() -> GMSPlace? {
var thisPlace: GMSPlace?
placesClient?.currentPlace(callback: { (placeLikelihoods, error) -> Void in
guard error == nil else {
print("Current Place error: \(error!.localizedDescription)")
return
}
if let placeLikelihoods = placeLikelihoods {
let place = placeLikelihoods.likelihoods.first?.place
thisPlace = place
}
})
print("Out of the brackets...")
return thisPlace
}
}
Anybody know why this might be happening?
Fixed it. Here is the code I used for the method in my LocationAPIService class:
func setCurrentLocationPlace(completion: #escaping (_ result: Bool)->()) {
var placeFindComplete: Bool = false
placesClient?.currentPlace(callback: { (placeLikelihoods, error) -> Void in
guard error == nil else {
print("Current Place error: \(error!.localizedDescription)")
completion(true)
return
}
if let placeLikelihoods = placeLikelihoods {
let place = placeLikelihoods.likelihoods.first?.place
self.currentPlace = place
placeFindComplete = true
completion(true)
}
})
if (placeFindComplete == false) {
completion(false)
}
}
And here is the way I called it from the view controller:
locationAPIService?.setCurrentLocationPlace() { (locationFound) -> () in
if (locationFound == true) {
//Run code here that triggers once setCurrentLocationPlace() complete.
}

Swift check type of object

I want to compare types of objects in swift.
I've got a function which takes an object of NSError as parameter. It should return a custom string.
It looks like this:
static func getLocalizedErrorText(error: NSError) -> String{
switch error {
case is NoConnection: //class NoConnection: NSError
return "....."
...
}
But the function is not working as expected. I think the main problem is that this example is not working:
var dummy = MySubError() //class MySubError: MyBaseError
var dummy2: MyBaseError?
dummy2 = MySubError()
if dummy.dynamicType == MySubError.self {
//This will work
}
if dummy2.dynamicType == MySubError.self {
//This will not work
}
How can I check which type the parameter got?
You can check for a type using
if error is MySubError {
// do stuff
}
You can also do an optional cast, which will succeed, if the type matches or return nil, if not:
let subError = error as? MySubError
which you can also use in a guard predicate or if let statement:
if let subError = error as? MySubError {
// do stuff
}
or
guard let subError = error as? MySuberror else { return }

fatal error: can't unsafeBitCast between types of different sizes (using gamekit)

Using GameKit Multiplayer feature (EasyGameCenter) found here: https://github.com/DaRkD0G/Easy-Game-Center-Swift
Upon two players connecting I get a crash on this line
let playerIDs = match.players.map { $0 .playerID } as! [String]
With this in console
fatal error: can't unsafeBitCast between types of different sizes
Any ideas? Here is full function for easy reference:
#available(iOS 8.0, *)
private func lookupPlayers() {
guard let match = EGC.sharedInstance.match else {
EGC.printLogEGC("No Match")
return
}
let playerIDs = match.players.map { $0 .playerID } as! [String]
/* Load an array of player */
GKPlayer.loadPlayersForIdentifiers(playerIDs) {
(players, error) in
guard error == nil else {
EGC.printLogEGC("Error retrieving player info: \(error!.localizedDescription)")
EGC.disconnectMatch()
return
}
guard let players = players else {
EGC.printLogEGC("Error retrieving players; returned nil")
return
}
if EGC.debugMode {
for player in players {
EGC.printLogEGC("Found player: \(player.alias)")
}
}
if let arrayPlayers = players as [GKPlayer]? { self.playersInMatch = Set(arrayPlayers) }
GKMatchmaker.sharedMatchmaker().finishMatchmakingForMatch(match)
(Static.delegate as? EGCDelegate)?.EGCMatchStarted?()
}
}
The problem is that your map statement is resulting in a type of Array<String?> because playerID is a String?, which you can't cast directly to Array<String>.
If you're certain you will always have a playerID value, you could change the statement
match.players.map { $0.playerID }
to:
match.players.map { $0.playerID! }
If you're not certain of that, then you can either use the Array<String?> value with appropriate optional handling or strip out the nil values by switching from map to flatMap:
match.players.flatMap { $0.playerID }