I have function below which returns courses per user:
func CoursesPerUser(controler: UIViewController, completionHandler: #escaping (Result<[Course]>) -> Void){
Alamofire.request(Constants.API.Users + UserId() + Constants.API.CoursesPerUser + Token(), encoding: JSONEncoding.default).responseJSON { response in
guard response.result.error == nil else {
print(response.result.error!)
completionHandler(.failure(response.result.error!))
return
}
guard let json = response.result.value as? [[String: AnyObject]] else {
print("Didn't get course objects as JSON from API")
completionHandler(.failure(BackendError.objectSerialization(reason: "Did not get JSON array in response")))
return
}
var courses:[Course] = []
for element in json {
if let courseResult = Course(json: element) {
courses.append(courseResult)
}
}
completionHandler(.success(courses))
}
}
When I call the function response has value of nil.
NetworkManager.sharedInstance.CoursesPerUser(controler: controler) { response in
print("Size of courses" + String(describing: response.value?.count))
}
I have printed each element in function CoursesPerUser and everything works fine. I wonder if I am not handling result in the good manner.
I have solved this problem. The solution is below.
func CoursesPerUser(completionHandler: #escaping ([Course]?, NSError?) -> ()){
Alamofire.request(Constants.API.Users + UserId() + Constants.API.CoursesPerUser + Token(), encoding: JSONEncoding.default).responseJSON { response in
switch response.result {
case .success :
if let result: AnyObject = response.result.value as AnyObject? {
if(response.response?.statusCode == 200){
var courses:[Course] = []
if let array = JSON(result).array {
for element in array {
if let course = Course(json: element) {
courses.append(course)
}
}
}
completionHandler(courses, nil)
} else {
if let message = JSON(result)["message"].string {
print("Logg: " + message)
}
}
}
case .failure(let error):
completionHandler(nil, error as NSError?)
}
}
}
NetworkManager.sharedInstance.CoursesPerUser() { courses, error in
if(error == nil) {
print("Size of courses" + String(describing: courses.count))
} else {
print("Log: " + String(describing: error))
}
}
Related
Here is how I add new entity.
func addCountry(name: String, code: String, flagImageUri: String?, wikiDataId: String) {
let newCountry = CountryEntity(context: container.viewContext)
newCountry.name = name
newCountry.code = code
newCountry.flagImageUri = flagImageUri
newCountry.wikiDataId = wikiDataId
save()
}
Here is my data:
However when I use the add function in my view, I got this error:
CoreData: error: +[CountryEntity entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
And this is my button:
Button(action: {
country.isFaved = !country.isFaved
coreDataModel.addCountry(name: country.name, code: country.code, flagImageUri: country.flagImageUri, wikiDataId: country.wikiDataId)
}) {
Image(systemName: "star.fill")
.foregroundColor(country.isFaved ? .black : .white)
.scaledToFit()
}
This is the whole class. I'm fetching, saving ,adding and deleting all data here. I did everything like the video I watched in youtube.
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "CountryCoreData")
#Published var savedCountries: [CountryEntity] = []
init() {
container.loadPersistentStores(completionHandler: { _, error in
if let error = error {
print("CoreData failed to load: \(error.localizedDescription)")
} else {
print("Successfully loaded")
}
})
}
func fetchCountries() -> [CountryEntity]? {
let request = NSFetchRequest<CountryEntity>(entityName: "CountryEntity")
do {
let fetchedCountries = try container.viewContext.fetch(request)
return fetchedCountries
} catch {
print("Something went wrong while data fetching \(error)")
return nil
}
}
func delete(code: String) {
guard let fetchedCountries = fetchCountries() else { return }
for country in fetchedCountries {
if country.code!.contains(code) {
container.viewContext.delete(country)
save()
}
}
}
func addCountry(name: String, code: String, flagImageUri: String?, wikiDataId: String) {
let newCountry = CountryEntity(context: container.viewContext)
print("OSMAN")
newCountry.name = name
newCountry.code = code
newCountry.flagImageUri = flagImageUri
newCountry.wikiDataId = wikiDataId
save()
}
func save() {
do {
try container.viewContext.save()
fetchCountries()
} catch {
print("Error while saving the data: \(error)")
}
}
}
How can I solve this problem?
I need help with currency exchange rate lookup given a key (3 digit currency code). The JSON object is rather unusual with no lablels such as date, timestamp, success, or rate. The first string value is the base or home currency. In the example below it is "usd" (US dollars).
I would like to cycle through all the currencies to get each exchange rate by giving its 3 digit currency code and storing it in an ordered array.
{
"usd": {
"aed": 4.420217,
"afn": 93.3213,
"all": 123.104693,
"amd": 628.026474,
"ang": 2.159569,
"aoa": 791.552347,
"ars": 111.887966,
"aud": 1.558363,
"awg": 2.164862,
"azn": 2.045728,
"bam": 1.9541,
"bbd": 2.429065,
"bch": 0.001278
}
}
In a slightly different formatted JSON object I used the following loop to copy exchange rates to an ordered array.
for index in 0..<userData.rateArray.count {
currencyCode = currCode[index]
if let unwrapped = results.rates[currencyCode] {
userData.rateArray[index] = 1.0 / unwrapped
}
}
The follow code is the API used to get the 3 digit currency codes and the exchange rates (called via UpdateRates).
class GetCurrency: Codable {
let id = UUID()
var getCurrencies: [String : [String: Double]] = [:]
required public init(from decoder: Decoder) throws {
do{
print(#function)
let baseContainer = try decoder.singleValueContainer()
let base = try baseContainer.decode([String : [String: Double]].self)
for key in base.keys{
getCurrencies[key] = base[key]
}
}catch{
print(error)
throw error
}
}
}
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var selectedBaseCurrency: String = "usd"
func UpdateRates() {
let baseUrl = "https://cdn.jsdelivr.net/gh/fawazahmed0/currency-api#1/latest/currencies/"
let baseCur = selectedBaseCurrency // usd, eur, cad, etc
let requestType = ".json"
guard let url = URL(string: baseUrl + baseCur + requestType) else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
do{
let decodedResponse = try JSONDecoder().decode(GetCurrency.self, from: data)
DispatchQueue.main.async {
self.results = decodedResponse
// this prints out the complete table of currency code and exchange rates
print(self.results?.getCurrencies["usd"] ?? 0.0)
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
Thanks lorem ipsum for your help. Below is the updated ASI logic that copies the exchange rates to the rateArray using key/value lookups.
class CurrencyViewModel: ObservableObject{
#Published var results: GetCurrency?
#Published var rateArray = [Double] ()
init() {
if UserDefaults.standard.array(forKey: "rates") != nil {
rateArray = UserDefaults.standard.array(forKey: "rates") as! [Double]
}else {
rateArray = [Double] (repeating: 0.0, count: 160)
UserDefaults.standard.set(self.rateArray, forKey: "rates")
}
}
func updateRates(baseCur: String) {
...
DispatchQueue.main.async {
self.results = decodedResponse
// loop through all available currencies
for index in 0..<currCode.count {
currencyCode = currCode[index]
// spacial handling for base currency
if currencyCode == baseCur {
self.rateArray[index] = 1.0000
} else {
let homeRate = self.results?.getCurrencies[baseCur]
// complement and save the exchange rate
if let unwrapped = homeRate?[currencyCode] {
self.rateArray[index] = 1.0 / unwrapped
}
}
}
}
} catch {
//Error thrown by a try
print(error)//much more informative than error?.localizedDescription
}
}
if error != nil {
//data task error
print(error!)
}
}.resume()
}
}
here is something that keeps me awake for three days already: I'm writing a little app that connects via BlueTooth to an Arduino. To get visual feedback about the connection state and the transmitted data, I use a view that allows me to connect/disconnect as well as shows me the state and data:
VStack {
Text("Glove Training App")
.font(.title)
HStack {
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange)
.font(.subheadline)
.padding(.bottom, 30)
Text(peripheral.transmittedString)
.font(.subheadline)
.padding(.bottom, 30)
}
}
In a separate file I have all the BT management:
class MyBluetoothManager: NSObject, ObservableObject {
#Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
(...)
func setConnected(peripheral: CBPeripheral) {
(...)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
class MyPeripheralDelegate: NSObject, ObservableObject, CBPeripheralDelegate {
let objectWillChange = ObservableObjectPublisher()
var transmittedString: String = "No data" {
willSet { objectWillChange.send()
}
}
func peripheral(_ peripheral: CBPeripheral,
didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
(...)
let rxData = characteristic.value
if let str = NSString(data: rxData!, encoding: String.Encoding.utf8.rawValue) as String? {
print(str)
self.transmittedString = str
let measurement = str.components(separatedBy: "|")
(...)
} else {
print("not a valid UTF-8 sequence")
}
}
}
The values are initially set correctly, but then never updated. In the terminal I can see the printed values and the app works otherwise as expected. I'm on the latest version of XCode.
I looked at several tutorials, and this seems to be tricky. Any help would be highly appreciated.
Cheers,
Christian
EDIT: Here is the full BluetoothManager class (not my code mostly but works fine):
class MyBluetoothManager: NSObject, ObservableObject {
#Published var stateChange: String = "Initializing..." {
willSet { objectWillChange.send() }
}
static let shared = MyBluetoothManager()
let central = CBCentralManager(delegate: MyCentralManagerDelegate.shared,
queue: nil, options: [
CBCentralManagerOptionRestoreIdentifierKey: restoreIdKey,
])
var state = State.poweredOff
enum State {
case poweredOff
case restoringConnectingPeripheral(CBPeripheral)
case restoringConnectedPeripheral(CBPeripheral)
case disconnected
case scanning(Countdown)
case connecting(CBPeripheral, Countdown)
case discoveringServices(CBPeripheral, Countdown)
case discoveringCharacteristics(CBPeripheral, Countdown)
case connected(CBPeripheral)
case outOfRange(CBPeripheral)
var peripheral: CBPeripheral? {
switch self {
case .poweredOff: return nil
case .restoringConnectingPeripheral(let p): return p
case .restoringConnectedPeripheral(let p): return p
case .disconnected: return nil
case .scanning: return nil
case .connecting(let p, _): return p
case .discoveringServices(let p, _): return p
case .discoveringCharacteristics(let p, _): return p
case .connected(let p): return p
case .outOfRange(let p): return p
}
}
}
func scan() {
guard central.state == .poweredOn else {
self.stateChange = "Cannot scan, BT is not powered on"
print("Cannot scan, BT is not powered on")
return
}
central.scanForPeripherals(withServices: [myDesiredServiceId], options: nil)
state = .scanning(Countdown(seconds: 10, closure: {
self.central.stopScan()
self.state = .disconnected
self.stateChange = "Scan timed out"
print("Scan timed out")
}))
}
func disconnect(forget: Bool = false) {
if let peripheral = state.peripheral {
central.cancelPeripheralConnection(peripheral)
}
if forget {
UserDefaults.standard.removeObject(forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
}
self.stateChange = "Disconnected"
state = .disconnected
}
func connect(peripheral: CBPeripheral) {
central.connect(peripheral, options: nil)
state = .connecting(peripheral, Countdown(seconds: 10, closure: {
self.central.cancelPeripheralConnection(peripheral)
self.state = .disconnected
self.stateChange = "Connect timed out"
print("Connect timed out")
}))
}
func discoverServices(peripheral: CBPeripheral) {
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverServices([myDesiredServiceId])
state = .discoveringServices(peripheral, Countdown(seconds: 10, closure: {
self.disconnect()
self.stateChange = "Could not discover services"
print("Could not discover services")
}))
}
func discoverCharacteristics(peripheral: CBPeripheral) {
guard let myDesiredService = peripheral.myDesiredService else {
self.disconnect()
return
}
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.discoverCharacteristics([myDesiredCharacteristicId],
for: myDesiredService)
state = .discoveringCharacteristics(peripheral, Countdown(seconds: 10,
closure: {
self.disconnect()
self.stateChange = "Could not discover characteristics"
print("Could not discover characteristics")
}))
}
func setConnected(peripheral: CBPeripheral) {
guard let myDesiredCharacteristic = peripheral.myDesiredCharacteristic
else {
self.stateChange = "Missing characteristic"
print("Missing characteristic")
disconnect()
return
}
UserDefaults.standard.set(peripheral.identifier.uuidString,
forKey: peripheralIdDefaultsKey)
UserDefaults.standard.synchronize()
peripheral.delegate = MyPeripheralDelegate.shared
peripheral.setNotifyValue(true, for: myDesiredCharacteristic)
state = .connected(peripheral)
self.stateChange = "Connected"
print("Connected")
}
}
Button(action: { MyBluetoothManager.shared.scan() }) {
Text("Connect")
.padding(30)
}
Text(" | ")
Button(action: { MyBluetoothManager.shared.disconnect()}) {
Text("Disconnect")
.padding(30)
}
}
Text(manager.stateChange) << why don't you use MyBluetoothManager.shared here ? is there a second instance? this might be the error...but unfortunately you just showed us a small piece of code...
How i can handle error in completion block if get error from server side.Here is object mapper class for login.
class Login: Mappable {
var result: LoginData?
var success: Bool?
var error: String?
required init?(map: Map){
}
func mapping(map: Map) {
result <- map["data"]
success <- map["success"]
error <- map["error"]
}
}
class LoginData: Mappable {
var name: String?
var title: String?
var token: String?
var imageUrl: String?
required init?(map: Map){
}
func mapping(map: Map) {
name <- map["name"]
title <- map["title"]
token <- map["token"]
name <- map["name"]
imageUrl <- map["imageUrl"]
}
}
Here is my api calling from view controller.
func loginMethod(){
let postData = ["username":loginDict.object(forKey: KUserUserId) as! String,
"password": loginDict.object(forKey: KUserPass) as! String]
userLoginHttp(parameters: postData){ completion in
self.getUserLoginResponse(result: completion)
}
}
func getUserLoginResponse(result: LoginData) {
// Here i do further
}
This is Network class mthod from where i fire the service
//User login web service
func userLoginHttp(parameters:Parameters, completion:#escaping (_
result:LoginData)->()) {
let defaultObject = UserDefaults.standard
var headerToken = String()
if let x = defaultObject.object(forKey: KDeviceToken)
{
headerToken = x as! String
}
else{
headerToken = ""
}
let headers = ["fcmToken": headerToken]
Alamofire.request(KLoginUrl, method: .post, parameters: parameters,encoding: JSONEncoding.default, headers: headers)
.validate()
.responseObject{ (response: DataResponse<Login>) in
switch response.result{
case .success:
let value = response.result.value
completion((value?.result)!)
case .failure(let error):
print(error.localizedDescription)
}
}
}
So how i can handle error in my view controller completion block?
you can just create another handler for errors and call it in error case
func userLoginHttp(parameters:Parameters,
completion:#escaping (_ result:LoginData)->(),
errorHandler:#escaping (_ result:Error,_ statusCode:Int?)->()//error handler
) {
let defaultObject = UserDefaults.standard
var headerToken = String()
if let x = defaultObject.object(forKey: KDeviceToken)
{
headerToken = x as! String
}
else{
headerToken = ""
}
let headers = ["fcmToken": headerToken]
Alamofire.request(KLoginUrl, method: .post, parameters: parameters,encoding: JSONEncoding.default, headers: headers)
.validate()
.responseObject{ (response: DataResponse<Login>) in
switch response.result{
case .success:
let value = response.result.value
completion((value?.result)!)
case .failure(let error):
errorHandler(error,response.response?.statusCode)//call error handler
print(response.response?.statusCode)
print(error.localizedDescription)
}
}
}
usage
func loginMethod(){
let postData = ["username":loginDict.object(forKey: KUserUserId) as! String,
"password": loginDict.object(forKey: KUserPass) as! String]
userLoginHttp(
parameters: postData,
completion:{ completion in
self.getUserLoginResponse(result: completion)
},
errorHandler:{ error,code in
//do your error stuff
})
}
func getUserLoginResponse(result: LoginData) {
// Here i do further
}
I use a general CoreData query method in my project.
func query(table: String, searchPredicate: NSPredicate) -> [AnyObject]
{
let context = app.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: table)
fetchRequest.predicate = searchPredicate
let results = try! context.fetch(fetchRequest)
return results
}
In Swift 3 this doesn't work. I found this on Apple's web site:
func findAnimals()
{
let request: NSFetchRequest<Animal> = Animal.fetchRequest
do
{
let searchResults = try context.fetch(request)
... use(searchResults) ...
}
catch
{
print("Error with request: \(error)")
}
}
Using the Apple example, how would I pass Animal in to the method as a parameter to make findAnimals more generic?
I haven't tried this but I think something like this would work...
func findCoreDataObjects<T: NSManagedObject>() -> [T] {
let request = T.fetchRequest
do
{
let searchResults = try context.fetch(request)
... use(searchResults) ...
}
catch
{
print("Error with request: \(error)")
}
}
You have to make the entire function generic and so you have to tell it what type T is when calling it.
someObject.findCoreDataObjects<Animal>()
I think that should do the job. Not entirely certain though as I'm new to generics myself :D
How about this.
func query<T: NSManagedObject>(table: String, searchPredicate: NSPredicate) -> [T] {
let context = app.managedObjectContext
let fetchRequest: NSFetchRequest<T> = NSFetchRequest(entityName: table)
fetchRequest.predicate = searchPredicate
let results = try! context.fetch(fetchRequest)
return results
}
Here is the final result that may help someone:
import Foundation
import Cocoa
func addRecord<T: NSManagedObject>(_ type : T.Type) -> T
{
let entityName = T.description()
let context = app.managedObjectContext
let entity = NSEntityDescription.entity(forEntityName: entityName, in: context)
let record = T(entity: entity!, insertInto: context)
return record
}
func recordsInTable<T: NSManagedObject>(_ type : T.Type) -> Int
{
let recs = allRecords(T.self)
return recs.count
}
func allRecords<T: NSManagedObject>(_ type : T.Type, sort: NSSortDescriptor? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func query<T: NSManagedObject>(_ type : T.Type, search: NSPredicate?, sort: NSSortDescriptor? = nil, multiSort: [NSSortDescriptor]? = nil) -> [T]
{
let context = app.managedObjectContext
let request = T.fetchRequest()
if let predicate = search
{
request.predicate = predicate
}
if let sortDescriptors = multiSort
{
request.sortDescriptors = sortDescriptors
}
else if let sortDescriptor = sort
{
request.sortDescriptors = [sortDescriptor]
}
do
{
let results = try context.fetch(request)
return results as! [T]
}
catch
{
print("Error with request: \(error)")
return []
}
}
func deleteRecord(_ object: NSManagedObject)
{
let context = app.managedObjectContext
context.delete(object)
}
func deleteRecords<T: NSManagedObject>(_ type : T.Type, search: NSPredicate? = nil)
{
let context = app.managedObjectContext
let results = query(T.self, search: search)
for record in results
{
context.delete(record)
}
}
func saveDatabase()
{
let context = app.managedObjectContext
do
{
try context.save()
}
catch
{
print("Error saving database: \(error)")
}
}
Call it with:
let name = "John Appleseed"
let newContact = addRecord(Contact.self)
newContact.contactNo = 1
newContact.contactName = name
let contacts = query(Contact.self, search: NSPredicate(format: "contactName == %#", name))
for contact in contacts
{
print ("Contact name = \(contact.contactName), no = \(contact.contactNo)")
}
deleteRecords(Contact.self, search: NSPredicate(format: "contactName == %#", name))
recs = recordsInTable(Contact.self)
print ("Contacts table has \(recs) records")
saveDatabase()
I use that way in my projects:
static func retrieveRecords<T: NSManagedObject>(table: String, sortDescriptorKey: NSSortDescriptor? = nil) -> [T] {
do {
let fetchRequest: NSFetchRequest<T> = NSFetchRequest(entityName: table)
fetchRequest.sortDescriptors = [sortDescriptorKey!]
let results = try context.fetch(fetchRequest)
print("\(results)")
return results
} catch let error {
print("Could not fetch \(error.localizedDescription)")
return []
}
}
And to call it:
personen = retrieveRecords(table: "Person", sortDescriptorKey: NSSortDescriptor(key: #keyPath(Person.nachname), ascending: true, selector: #selector(NSString.localizedCompare)))