SWIFT2.0: function returns before task is completed - serialization

I'm pretty sure this is easy for most of you, but i'm a beginner and i cannot figure this one out.
I build a function to parse a JSON file online. That function should returns a String once the file has been parsed, but it does not wait for the task to be completed before "return". Therefore, i always end up with a wrong value.
my function:
func getJsonDataFromPath(path: String) -> String {
var videoId: String
videoId = "++ empty ++"
let url = NSURL(string:path)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
print("HTTP RESPONSE: \(httpResponse.statusCode)")
} // END OF if let httpResponse = response as? NSHTTPURLResponse
let json = JSON(data: data!)
// print(json)
if (json.count > 0) {
videoId = json[0]["videoId"].string!
print("videoId is: \(videoId)")
}
}
task.resume()
return videoId
}
and its call:
override func viewDidLoad() {
super.viewDidLoad()
let test = getJsonDataFromPath("http://coulon.xyz/JobX/APIs/getListOfJobs.php")
print("Value returned by getJsonDataFromPath: \(test)")
}
I always get the output in the wrong order:
Value returned by getJsonDataFromPath: ++ empty ++
HTTP RESPONSE: 200
videoId is: kzv1NQGdsyk
How can i make sure task.resume is completed before returning the value ?
Thank a lot in advance,
Regards,
Julien

You should implement Swift closure like that:
func getJsonDataFromPath(path: String, completion: (item: String)-> Void){
var videoId: String
videoId = "++ empty ++"
let url = NSURL(string:path)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(url!) { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
print("HTTP RESPONSE: \(httpResponse.statusCode)")
} // END OF if let httpResponse = response as? NSHTTPURLResponse
let json = JSON(data: data!)
// print(json)
if (json.count > 0) {
videoId = json[0]["videoId"].string!
print("videoId is: \(videoId)")
completion(item: videoId)
}
}
task.resume()
}
And its call:
override func viewDidLoad() {
super.viewDidLoad()
getJsonDataFromPath("http://coulon.xyz/JobX/APIs/getListOfJobs.php") { (test) -> Void in
print("Value returned by getJsonDataFromPath: \(test)")
}
}

Related

Problem Using a Switch to Set A TimeInterval

I'm have a hard time creating a user setting options. I would like the user to customize the frequency of the timer to receive the local notifications. I'm using a switch on the SystemSettingsVC to for the user to select and set the user default and I'm using the user default setting in my MainVC for the TimerInterval. My app runs but the time doesnt change. I know that the switch is working because I'm also testing the background color change.
Here is my code for my SystemSettingsVC:
...
import UIKit
import SwiftUI
import CoreData
class SettingsViewController: UIViewController {
#IBOutlet weak var timeSelection: UISegmentedControl!
let userDefaults = UserDefaults.standard
let TIME_KEY = "TIME_KEY"
let ONE_HOUR_KEY = 60.0
let THREE_HOUR_KEY = 120.0
let SIX_HOUR_KEY = 300.0
override func viewDidLoad() {
super.viewDidLoad()
updateTime()
}
func updateTime() {
let time = userDefaults.object(forKey: "TIME_KEY")
if(time as? Double == ONE_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 0
let userDefaults = UserDefaults.standard
userDefaults.set(60.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.white
save()
}
else if(time as? Double == THREE_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 1
let userDefaults = UserDefaults.standard
userDefaults.set(120.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.gray
save()
}
else if(time as? Double == SIX_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 2
let userDefaults = UserDefaults.standard
userDefaults.set(300.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.darkGray
save()
}
}
func save() {
if let savedData = try? NSKeyedArchiver.archivedData(withRootObject: clock, requiringSecureCoding: false){
let defaults = UserDefaults.standard
defaults.set(savedData, forKey: "TIME_KEY")
}
}
#IBAction func selectTimeOfQuotes(_ sender: Any) {
switch timeSelection.selectedSegmentIndex
{
case 0:
userDefaults.set(60.0, forKey: "TIME_KEY")
save()
case 1:
userDefaults.set(120.0, forKey: "TIME_KEY")
save()
case 2:
userDefaults.set(300.0, forKey: "TIME_KEY")
save()
default:
userDefaults.set(60.0, forKey: "TIME_KEY")
save()
}
updateTime()
}
}
...
Here is the code for my view controller to where I call the user defaults, I placed let userDefaults = UserDefaults.standard in my ViewDidLoad :
'''Code''' ```
func configureAlerts() {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications()
center.removeAllPendingNotificationRequests()
let listQuotes = quotes
let i = 1
let content = UNMutableNotificationContent()
content.title = “Inspire”
content.body = listQuotes[i].shareMessage
content.sound = UNNotificationSound.default
let alertDate = Date().byAdding(days: i)
var alertComponents = Calendar.current.dateComponents([.day, .month, .year], from: alertDate)
alertComponents.hour = 8
let userDefaults = UserDefaults.standard
typealias NSTimeInterval = Double
let thisTime:TimeInterval = userDefaults.double(forKey: "TIME_KEY")
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: thisTime, repeats: true)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
You are not showing how your Models are connected so we can't tell where the miscommunication is happening or maybe that is the issue. They are not connected.
But at a simple glance you are not rescheduling the notifications. selectTimeOfQuotes, save or updateTime do not call configureAlerts.
Something to note you have a lot of repeating code and hardcoded values that could be the source of the confusion.
BTW 120 is 2 hours not 3 idk if that is on purpose but it highlights my next point.
When you change a value you only want to do it in 1 place; if possible; so centralizing the models will help you avoid having to change things in multiple places.
For the options for your picker an enum can hold everything.
enum NotificationInterval: Double, CaseIterable, Codable{
case ONE_HOUR_KEY = 3660 //TimeInterval == seconds
case THREE_HOUR_KEY = 10800 //TimeInterval == seconds
case SIX_HOUR_KEY = 21600 //TimeInterval == seconds
func label() -> String{
var result = ""
switch self {
case .ONE_HOUR_KEY:
result = "1 hour"
case .THREE_HOUR_KEY:
result = "3 hours"
case .SIX_HOUR_KEY:
result = "6 hours"
}
return result
}
func color() -> UIColor{
var result = UIColor.label
switch self {
case .ONE_HOUR_KEY:
result = UIColor.white
case .THREE_HOUR_KEY:
result = UIColor.gray
case .SIX_HOUR_KEY:
result = UIColor.darkGray
}
return result
}
///Key for storage of user selected interval
static var userDefaultKey: String{
"TIME_KEY"
}
///Saves value to store using the `userDefaultKey`
func saveToStore(){
var mgr = UserDefaultManager()
mgr.intervalTime = self
}
///Gets value from store using the `userDefaultKey`
static func getFromStore() -> NotificationInterval{
let raw = UserDefaultManager().intervalTime
return raw
}
///Gets the index for the object in the `allCases` array
func getAllCasesIndex() -> Int?{
NotificationInterval.allCases.firstIndex(where: {
self == $0
})
}
///Gets the index for the `userDefaultKey` stored object in the `allCases` array
static func getStoredIndex() -> Int?{
NotificationInterval.getFromStore().getAllCasesIndex()
}
}
Then since you have at least 2 unrelated classes that use the value store in user defaults you can centralize that work too
///This stores and retreives userdefaults to a predetermined store
struct UserDefaultManager{
//Having a single location for this will simplify UserDefault storage
//A use case would be switching to an App Group store when you decide to support watch in the future or if you want to add Widgets
private let store = UserDefaults.standard
///User selected interval for the notifications
var intervalTime: NotificationInterval{
get{
getObject(forKey: NotificationInterval.userDefaultKey, type: NotificationInterval.self) ?? NotificationInterval.ONE_HOUR_KEY
}
set{
save(newValue, forKey: NotificationInterval.userDefaultKey)
}
}
///Saves any Codable to UserDefaults
func save<T: Codable>(_ object: T, forKey: String){
let encoder = JSONEncoder()
do{
let encoded = try encoder.encode(object)
store.set(encoded, forKey: forKey)
}catch{
print(error)
}
}
//Gets any Codable from UserDefaults
func getObject<T: Codable>(forKey: String, type: T.Type) -> T?{
guard let saved = store.object(forKey: forKey) as? Data else {
return nil
}
let decoder = JSONDecoder()
do{
let loaded = try decoder.decode(T.self, from: saved)
return loaded
}catch{
print(error)
return nil
}
}
}
Then your SettingsViewController will look like this
class SettingsViewController: UIViewController {
///Programatic use of IBOutlet
var timeSelection: UISegmentedControl!
private let quoteManager = QuoteManager()
override func viewDidLoad() {
super.viewDidLoad()
//Create control PS I dont have a storyboard setup but you can replace this with your IBOutlet and IBAction
timeSelection = UISegmentedControl(items: NotificationInterval.allCases.map({
$0.label()
}))
timeSelection.addTarget(self, action: #selector(selectTimeOfQuotes), for: .allEvents)
//Set the initial value from storage
timeSelection.selectedSegmentIndex = NotificationInterval.getStoredIndex() ?? 0
self.view.addSubview(timeSelection)
timeSelection.translatesAutoresizingMaskIntoConstraints = false
timeSelection.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
timeSelection.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
//End of programatic setup
//Set the color from storage
view.backgroundColor = NotificationInterval.getFromStore().color()
}
///Programatic use of IBAction
#objc
func selectTimeOfQuotes() {
//Identify the selected interval
let interval = NotificationInterval.allCases[timeSelection.selectedSegmentIndex]
//Save it
interval.saveToStore()
//Change the color
view.backgroundColor = interval.color()
//Change the notification
quoteManager.rescheduleQuotes()
}
}
As the last line of code shows once all the work is done you should reschedule the quotes.
I created a mini-QuoteManager since you do not show this connection. This manager can be used by any View Controller to get the quotes and maybe even reschedule when the quotes change by calling the provided method.
//Adapt this to your use case this is just a sample
///Liason for quote Storege
struct QuoteManager{
var listQuotes = ["one", "two", "three"]
private let notificationManager = NotificationManager.shared
private let userDefaultsManager = UserDefaultManager()
///Reschedules quotes
func rescheduleQuotes(count: Int = 10){
let title = "Inspire"
notificationManager.deleteNotifications()
print(#function)
for n in 1..<count+1{
print(n)
let newDate = userDefaultsManager.intervalTime.rawValue*Double(n)
//Idenfier must be unique so I added the n
notificationManager.scheduleUNTimeIntervalNotificationTrigger(title: title, body: listQuotes.randomElement()!, timeInterval: newDate, identifier: "com.yourCompany.AppName.\(title)_\(n.description)")
}
}
}
The QuoteManager calls the NotificationManager. I created a small version below.
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
//Singleton is requierd because of delegate
static let shared: NotificationManager = NotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
private override init(){
super.init()
//This assigns the delegate
notificationCenter.delegate = self
requestAuthorization()
}
func scheduleUNTimeIntervalNotificationTrigger(title: String, body: String, timeInterval: TimeInterval, identifier: String, repeats: Bool = false){
print(#function)
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: repeats)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
notificationCenter.add(request) { (error) in
if error != nil {
print(error!)
}
self.printNotifications()
}
}
func requestAuthorization() {
print(#function)
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Access Granted!")
} else {
print("Access Not Granted")
}
}
}
func deleteNotifications(){
print(#function)
notificationCenter.removeAllPendingNotificationRequests()
notificationCenter.removeAllDeliveredNotifications()
}
///Prints to console schduled notifications
func printNotifications(){
print(#function)
notificationCenter.getPendingNotificationRequests { request in
print("UNTimeIntervalNotificationTrigger Pending Notification")
for req in request{
if req.trigger is UNTimeIntervalNotificationTrigger{
print((req.trigger as! UNTimeIntervalNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
print(req.content.body)
}
}
print("UNCalendarNotificationTrigger Pending Notification")
for req in request{
if req.trigger is UNCalendarNotificationTrigger{
print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
}
}
}
}
//MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.banner)
}
}
It might seem like a lot but if you focus on the SettingsViewController you will see how much simpler the whole thing becomes.
All this is working code. Just copy and paste into a .swift file.
You might have to change the UISegmentedControl since I created it programmatically but if you put the SettingsViewController in a blank storyboard it should work as is.

Completion block with multiple params in Kotlin

In my application I use Retrofit to make a network call and I pass a completion block to the wrapping function:
fun issueToken(phoneNumber: String, otp: String,completion:(token: Token?, errorString: String) -> Unit) { //TODO: return something??
retrofitInstance.issueToken(LOGIN_GRANT_TYPE, LOGIN_CLIENT_ID,LOGIN_CLIENT_SECRET,phoneNumber, otp).enqueue(object : retrofit2.Callback<IssueTokenResponse> {
override fun onFailure(call: retrofit2.Call<IssueTokenResponse>, t: Throwable) {
completion(null, "NetworkManger: issueToken failed")
}
override fun onResponse(call: retrofit2.Call<IssueTokenResponse>, response: retrofit2.Response<IssueTokenResponse>) {
if (response.code() == 200) {
AccountManager.token.mAccessToken = response.body()?.access_token
AccountManager.token.mRefreshToken = response.body()?.refresh_token
AccountManager.token.mExpirationDate =
response.body()?.expires_in?.plus((System.currentTimeMillis() / 1000L));
completion(AccountManager.token, "")
} else {
//TODO
completion(null, "NetworkManager: issueToken response code = " + response.code() + " message = " + response.message() )
}
}
})
}
Now, I'm not sure how to call the issueToken method with the completion block, as I am new to Kotlin. I tried something like that:
NetworkManager.issueToken(view.phoneNumber.text.toString(),view.otp_edit_text_id.text.toString()) token, errorString -> { ... }
But I get errors that says:
No value passed for parameter 'completion'
I also have errors on this part:
token, errorString ->
What is the correct way to call the function with the completion block?
I suggest you to read kotlin lambda conventions first.
You should call like that:
issueToken(view.phoneNumber.text.toString(),view.otp_edit_text_id.text.toString()) { token: Token?, errorString: String ->
}
You may also use like that:
val completion = { token: Token?, errorString: String ->
// define whatever you need
}
issueToken(view.phoneNumber.text.toString(),view.otp_edit_text_id.text.toString(), completion)

Swift: Unable to assign function return value to UILabel

I have a situation where a string returned from a function call is being assigned to a UILabel text. However, the UILabel is carrying an empty value. If I were to assign a static string viz. "this is testing" to the UILabel directly it works. Within the function, I can print the return value
import Foundation
import Firebase
import UIKit
class checkLocation{
var items = [addedItems]()
func getLocationOfItem(textFieldText: (String)) -> String{
let itemLet = addedItems()
var location = ""
Database.database().reference().child("item").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject]{
//itemLet.setValuesForKeys(dictionary)
itemLet.itemName = dictionary["itemName"] as? String
itemLet.itemLocation = dictionary["itemLocation"] as? String
itemLet.itemImageUrl = dictionary["itemImageUrl"] as? String
itemLet.itemTS = dictionary["itemTS"] as? String
itemLet.id = dictionary["id"] as? String
if(textFieldText == itemLet.itemName){
location = itemLet.itemLocation!
print(location)
}
}
}, withCancel: nil)
return location
}
}
Calling program
import UIKit
import Firebase
class checkLocationViewController: ViewController {
#IBOutlet weak var itemNameInLctn: UITextField!
#IBOutlet weak var itemLocationLbl: UILabel!
var items = [addedItems]()
var strLocation = "";
override func viewDidLoad() {
super.viewDidLoad()
refItems = Database.database().reference().child("item")
}
#IBAction func getLocation(_ sender: UIButton) {
self.strLocation = checkLocation().getLocationOfItem(textFieldText: itemNameInLctn.text!)
itemLocationLbl.text = strLocation
//itemLocationLbl.text = strLocation
//print(itemNameInLctn.text)
}
}
Any help?
The func getLocationOfItem(textFieldText: (String)) -> String is correctly returning location that has the initial value of "“.
The issue that you are facing is that, you are calling an an async task from firebase within a sync function call.
You can refactor the method call to return you the required string asynchronously be making the following changes:
func getLocationOfItem(textFieldText: String, completion: #escaping (_ itemLocation: String?) -> Void){
let itemLet = addedItems()
Database.database().reference().child("item").observe(.childAdded, with: { (snapshot) in
guard let dictionary = snapshot.value as? [String: AnyObject] else {
return completion(nil)
}
itemLet.itemName = dictionary["itemName"] as? String
itemLet.itemLocation = dictionary["itemLocation"] as? String
itemLet.itemImageUrl = dictionary["itemImageUrl"] as? String
itemLet.itemTS = dictionary["itemTS"] as? String
itemLet.id = dictionary["id"] as? String
if(textFieldText == itemLet.itemName){
completion(itemLet.itemLocation)
} else {
completion(nil)
}
}, withCancel: nil)
}
/// Usage
#IBAction func getLocation(_ sender: UIButton) {
guard let itemNameInLocation = itemNameInLctn.text else { return }
getLocationOfItem(textFieldText: itemNameInLocation) { [weak self] (itemLocation) in
self?.strLocation = itemLocation
self?.itemLocationLbl.text = itemLocation
}
}
Note:
Please be advised that the completion handler will be called every time that a value is added under the item path in your firebase database.
I would highly recommend refactoring further in this case to use the observeSingleEventthat firebase provides.
Please see link below for documentation about said method:
https://firebase.google.com/docs/database/ios/read-and-write#read_data_once
If this was your intention then its the best choice.

Alamofire 3.0 and swift 2.0 broke my APIWrapper

I recently updated to swift 2.0 and it destroyed my alamofire serializer and API wrapper.
extension Alamofire.Request {
class func authResponseSerializer() -> Serializer {
return {request, response, data in
if data == nil {
return (nil, nil)
}
var jsonError: NSError?
let jsonData:AnyObject?
do {
jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: [])
} catch var error as NSError {
jsonError = error
jsonData = nil
} catch {
fatalError()
}
if jsonData == nil || jsonError != nil {
return (nil, jsonError)
}
let json = JSON(jsonData!)
if json.error != nil || json == nil {
return (nil, json.error)
}
var auth:Authorization = Authorization(json: json)
return (auth, nil)
}
}
func responseAuthorization(completionHandler: (NSURLRequest, NSHTTPURLResponse?, Result<Authorization>) -> Void) -> Self {
return response(responseSerializer: Request.authResponseSerializer(), completionHandler: completionHandler)
}
in the request extension i get the error
and in the second func responseAuthoriztion
Use of undeclared type 'Serializer'
I get the error generic type Result specialized with too few type parameters (got 1, but expected 2)
How do i migrate my code to alamofire 3.0 and swift 2.0? I've been reading the migration guides and can't figure this out, many other people I'm sure are dealing with this same problem.

how to serialize json response to dictionary in alamofire 2 with swift 2 without swifty json

This code used to work in the previousversion of alamofire before swift 2. Now it gives a warning: cast from Result<AnyObject> to Dictionary<String, AnyObject> always fails.
Alamofire.Manager.sharedInstance.request(.POST, url, parameters:params)
.responseJSON { (request, response, data) -> Void in
var result = data as? Dictionary<String,AnyObject> //this gives an error cast from Result<AnyObject> to Dictionary<String, AnyObject> always fails
How can I get the cast to dictionary working?
You need to call:
Alamofire.request(.POST, url, parameters:params)
.responseJSON { request, response, result in
debugPrint(result)
if let value = result.value as? [String: AnyObject] {
print(value)
}
}
You should read through the updated README code samples.
I know it's bit too late to answer this but I share this because I feel like maybe this code can help someone:
Alamofire.request(url, method: .post, parameters: param, encoding: JSONEncoding.default, headers: nil).responseJSON
{
response in
SVProgressHUD.dismiss()
let data = response.result.value
let responseObject = data as? NSDictionary
switch (response.result)
{
case .success(_):
print(responseObject!["message"] as! NSString as String)
break
case .failure(_):
SVProgressHUD.showError(withStatus: (responseObject!["message"] as! NSString as String))
print(responseObject!["message"] as! NSString as String)
break
}
}
Thanks and Enjoy! Happy coding! :)