swift use class instances to implement variable - class

My aim is to implement first a viewtable with json data in a view controller.
Then in another viewcontroller i want to choose some record from table to view.
First my class Route is above
class Route {
var id: Int?
var travelTimeSeconds: Int?
var condition:String?
var lastUpdate:String?
var title:String?
var backup = [Route]()
init(json: NSDictionary) {
self.id = json["id"] as? Int
self.travelTimeSeconds = json["travelTimeSeconds"] as? Int
self.condition = json["condition"] as? String
self.lastUpdate = json["lastUpdate"] as? String
self.title = json["title"] as? String
}
init(){}
func findRoutes(predferedroute:String)->[Route] {
let jsonurl = NSURL(string: predferedroute)!
let task = NSURLSession.sharedSession().dataTaskWithURL(jsonurl) { (data,response, error) -> Void in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if let urlcontent = data {
do{
let jsonresult = try NSJSONSerialization.JSONObjectWithData(urlcontent, options: NSJSONReadingOptions.MutableContainers)
if let routeArray = jsonresult["result"] as? [NSDictionary] {
for item in routeArray {
self.backup.append(Route(json: item))
}//end for
}//end if
}//end do
catch {
print("Serialization error")
}//end catch
}//end if
})//end dispatch
}//end task
task.resume()
return self.backup
}//end findRoutes
}
in the first view cotroller i write the following code
class MenuPageViewController: UIViewController{
var routes = [Route]()
var implementroutes = Route()
var allroutes:String = "http://quickweb.gr/itsweb/webservices/json?op=GetAll"
var activeRoutes:String = "http://quickweb.gr/itsweb/webservices/json?op=GetActive"
override func viewDidLoad() {
super.viewDidLoad()
NSUserDefaults.standardUserDefaults().setObject("palios55", forKey: "StartUp")
let nickname1 = NSUserDefaults.standardUserDefaults().objectForKey("StartUp")!
print(nickname1)
routes = implementroutes.findRoutes(allroutes)
routes = implementroutes.backup
}//end viewload
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
the other delegates for table view works
but still remain nil the routes for both of two codes:
routes = implementroutes.findRoutes(allroutes)
routes = implementroutes.backup
Does i miss something with viewcontrollers and classes?
if i use the same code directly in tableview it works.

Related

save string over saved string in core data

In my swift code below the code saves an item in core data. The goal is to overwrite that item. I am getting a runtime error at
CoreDataHandler.changeName(user: fetchUser!\[indexNumber\], jessica: "jo")
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
I don't know how to wrap in the index number. The goal is it to print judo then jo
import UIKit;import CoreData
class ViewController: UIViewController {
var fetchUser: [UserInfo]? = nil
var indexNumber : Int = 0
override func viewDidLoad() {
super.viewDidLoad()
CoreDataHandler.saveObject2( name: "judo")
getText(textNo: indexNumber)
saveTheItem()
}
#objc func saveTheItem(){
CoreDataHandler.changeName(user: fetchUser![indexNumber], jessica: "jo")
}
func getText(textNo:Int) {
// first check the array bounds
let info = helpText.shareInstance.fetchText()
if info.count > textNo {
if let imageData = info[textNo].name
{
print(imageData)
} else {
// no data
print("data is empty Textss")
}
} else {
// image number is greater than array bounds
print("you are asking out of bounds")
}
}
}
class CoreDataHandler : NSManagedObject {
class func saveObject2( name: String) -> Bool {
let context = getContext()
let entity = NSEntityDescription.entity(forEntityName: "UserInfo", in: context)
let managedObject = NSManagedObject(entity: entity!, insertInto: context)
managedObject.setValue(name, forKey: "name")
do{
try context.save()
return true
}
catch {
return false
}
}
private class func getContext() -> NSManagedObjectContext{
let appD = UIApplication.shared.delegate as! AppDelegate
return appD.persistentContainer.viewContext
}
class func changeName(user: UserInfo,jessica : String) -> Bool
{
let context = getContext()
user.name = jessica
print(jessica)
do{
try context.save()
return true
}
catch{
return false
}
}
}
class helpText: UIViewController{
private class func getContext() -> NSManagedObjectContext {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
return appDelegate.persistentContainer.viewContext
}
static let shareInstance = helpText()
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
func saveName(data: String) {
let imageInstance = UserInfo(context: context)
imageInstance.name = data
do {
try context.save()
} catch {
print(error.localizedDescription)
}
}
func fetchText() -> [UserInfo] {
var fetchingImage = [UserInfo]()
let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "UserInfo")
do {
fetchingImage = try context.fetch(fetchRequest) as! [UserInfo]
} catch {
print("Error while fetching the image")
}
return fetchingImage
}
}
No offense but your code is a mess.
And there is a big misunderstanding. Core Data records are unordered, there is no index. To update a record you have to fetch it by a known attribute, in your example by name, update it and save it back.
This is a simple method to do that. It searches for a record with the given name. If there is one, update the attribute with newName and save the record.
The code assumes that there is a NSManagedObject subclass UserInfo with implemented class method fetchRequest.
func changeName(_ name: String, to newName: String) {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let request : NSFetchRequest<UserInfo> = UserInfo.fetchRequest()
request.predicate = NSPredicate(format: "name == %#", name)
do {
let records = try context.fetch(request)
guard let foundRecord = records.first else { return }
foundRecord.name = newName
try context.save()
} catch {
print(error)
}
}
Regarding your confusing code:
Create CoreDataHandler as singleton (and it must not be a subclass of NSManagedObject). Move the Core Data related code from AppDelegate and the methods to read and write in this class.

How to load data in the collectionview from JSON to Swift by using Alamofire and Kingfisher

I need to get the data from already made JSON file to the Swift. By using MVVM desing I wrote this code in the Repo class
func loadFoods() {
AF.request("http://example.com/foods/getAllFoods.php",method: .get).response { response in
if let data = response.data {
do{
let result = try JSONDecoder().decode(ProductsResponse.self, from: data)
if let list = result.foods {
self.foodList.onNext(list)
}
}catch{
print(error.localizedDescription)
}
}
}
}
Here's the code from View Model class:
class HomeViewModel {
var foodList = BehaviorSubject <[Foods]>(value: [Foods]())
var frepo = FoodsDaoRepository()
init() {
loadFoods()
foodList = frepo.foodList
}
func loadFoods() {
frepo.loadFoods()
}
func loadPersons(){
prepo.loadPersons()
}
and I wrote this code in the ViewContoller class:
override func viewDidLoad() {
super.viewDidLoad()
searchTextField.delegate = self
collectionView.delegate = self
collectionView.dataSource = self
let _ = viewModel.foodList.subscribe(onNext: { list in
self.foodsList = list
DispatchQueue.main.async {
self.collectionView.reloadData()
}
})
override func viewWillAppear(_ animated: Bool) {
viewModel.loadFoods()
}
Foodlist variable takes the data from Food class:
class Foods: Codable {
var id : Int?
var name : String?
var image : String?
var price : Int?
var category : String?
init(id: Int?, name: String?, image: String?, price: Int?, category: String?) {
self.id = id
self.name = name
self.image = image
self.price = price
self.category = category
}
}
But it didn't help to get the data from JSON to the CollectionView. It just shows empty collection view cells.
Also how can I get an image by using Kingfisher and Alamofire?
I tried to explain the problem and wrote a code to expand my question

I can print data but can't assign it to a label in Swift

I sent my data from my API call to my InfoController viewDidLoad. There, I was able to safely store it in a skillName constant, and also printed it, receiving all the information by console.
The problem comes when I try to assign this variable to my skillLabel.
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
guard var skillName = names as? String else { return }
self.pokemon?.skillName = skillName
self.allNames = skillName
print(self.allNames)
}
}
There, when I print allNames, the console shows all the data I need. This is how the data looks like: Data Example
And the computed property where I wanna use this data looks is:
var pokemon: Pokemon? {
didSet {
guard let id = pokemon?.id else { return }
guard let data = pokemon?.image else { return }
navigationItem.title = pokemon?.name?.capitalized
infoLabel.text = pokemon?.description
infoView.pokemon = pokemon
if id == pokemon?.id {
imageView.image = UIImage(data: data)
infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: "\(allNames)")
}
}
}
PD: allNames is a String variable I have at InfoController class-level.
This is how my app looks when run:
PokeApp
My goal is to get that details param to show the skillName data, but it returns nil, idk why. Any advice?
EDIT1: My func that fetches the Pokemon data from my service class is this one:
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
DispatchQueue.main.async {
self.pokemon? = poke
guard let skills = poke.abilities else { return }
for skill in skills {
guard let ability = skill.ability else { return }
guard var names = ability.name!.capitalized as? String else { return }
self.pokemon?.skillName = names
handler(names)
}
}
}
}
EDIT2: InfoView class looks like:
class InfoView: UIView {
// MARK: - Properties
var delegate: InfoViewDelegate?
// This whole block assigns the attributes that will be shown at the InfoView pop-up
// It makes the positioning of every element possible
var pokemon: Pokemon? {
didSet {
guard let pokemon = self.pokemon else { return }
guard let type = pokemon.type else { return }
guard let defense = pokemon.defense else { return }
guard let attack = pokemon.attack else { return }
guard let id = pokemon.id else { return }
guard let height = pokemon.height else { return }
guard let weight = pokemon.weight else { return }
guard let data = pokemon.image else { return }
if id == pokemon.id {
imageView.image = UIImage(data: data)
}
nameLabel.text = pokemon.name?.capitalized
configureLabel(label: typeLabel, title: "Type", details: type)
configureLabel(label: pokedexIdLabel, title: "Pokedex Id", details: "\(id)")
configureLabel(label: heightLabel, title: "Height", details: "\(height)")
configureLabel(label: defenseLabel, title: "Defense", details: "\(defense)")
configureLabel(label: weightLabel, title: "Weight", details: "\(weight)")
configureLabel(label: attackLabel, title: "Base Attack", details: "\(attack)")
}
}
let skillLabel: UILabel = {
let label = UILabel()
return label
}()
let imageView: UIImageView = {
let iv = UIImageView()
iv.contentMode = .scaleAspectFill
return iv
}()
. . .
}
infoView.configureLabel is this:
func configureLabel(label: UILabel, title: String, details: String) {
let attributedText = NSMutableAttributedString(attributedString: NSAttributedString(string: "\(title): ", attributes: [NSAttributedString.Key.font : UIFont.boldSystemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: Colors.softRed!]))
attributedText.append(NSAttributedString(string: "\(details)", attributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 16), NSAttributedString.Key.foregroundColor: UIColor.gray]))
label.attributedText = attributedText
}
EDIT 3: Structures design
struct Pokemon: Codable {
var results: [Species]?
var abilities: [Ability]?
var id, attack, defense: Int?
var name, type: String?
...
}
struct Ability: Codable {
let ability: Species?
}
struct Species: Codable {
let name: String?
let url: String?
}
Jump to the Edit2 paragraph for the final answer!
Initial Answer:
I looks like you UI does not get updated after the controller fetches all the data.
Since all of you UI configuration code is inside the var pokemon / didSet, it's a good idea to extract it to a separate method.
private func updateView(with pokemon: Pokemon?, details: String?) {
guard let id = pokemon?.id, let data = pokemon?.image else { return }
navigationItem.title = pokemon?.name?.capitalized
infoLabel.text = pokemon?.description
infoView.pokemon = pokemon
if id == pokemon?.id {
imageView.image = UIImage(data: data)
infoView.configureLabel(label: infoView.skillLabel, title: "Skills", details: details ?? "")
}
}
and now you can easily call in the the didSet
var pokemon: Pokemon? {
didSet { updateView(with: pokemon, details: allNames) }
}
and fetchPokemons completion aswell
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
guard var skillName = names as? String else { return }
self.pokemon?.skillName = skillName
self.allNames = skillName
print(self.allNames)
DispatchQueue.main.async {
self.updateView(with: self.pokemon, details: self.allNames)
}
}
}
It's super important to do any UI setup on the main queue.
Edit:
The fetch function may be causing the problems! you are calling handler multiple times:
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
DispatchQueue.main.async {
self.pokemon? = poke
guard let skills = poke.abilities else { return }
let names = skills.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
handler(names)
}
}
}
Edit2:
After looking at your codebase there are a couple of things you need to change:
1. fetchPokemons implementation
the handler of controller.service.fetchPokes gets called for every pokemon so we need to check if the fetched one is the current (self.pokemon) and then call the handler with properly formated skills.
func fetchPokemons(handler: #escaping (String) -> Void) {
controller.service.fetchPokes { (poke) in
guard poke.id == self.pokemon?.id else { return }
self.pokemon? = poke
let names = poke.abilities?.compactMap { $0.ability?.name?.capitalized }.joined(separator: ", ")
handler(names ?? "-")
}
}
2. update viewDidLoad()
now simply pass the names value to the label.
override func viewDidLoad() {
super.viewDidLoad()
configureViewComponents()
fetchPokemons { (names) in
self.pokemon?.skillName = names
self.infoView.configureLabel(label: self.infoView.skillLabel, title: "Skills", details: names)
}
}
3. Refactor var pokemon: Pokemon? didSet observer
var pokemon: Pokemon? {
didSet {
guard let pokemon = pokemon, let data = pokemon.image else { return }
navigationItem.title = pokemon.name?.capitalized
infoLabel.text = pokemon.description!
infoView.pokemon = pokemon
imageView.image = UIImage(data: data)
}
}

Swift service class to retrieve Firebase data

I have a service class to fetch data from my Firebase:
class Service {
var myName = String?
var myDev: String?
func getData() {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Data").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
self.myName = name
let dev = value?["dev"] as? String ?? ""
self.myDev = dev
}) { (error) in
print(error.localizedDescription)
}
}
}
and realization in my Main class:
var service = Service()
override func viewDidLoad(){
super.viewDidLoad()
service.getData()
configureLabel()
}
private func configureLabel(){
self.titleLabel.text = service.myName
self.devLabel.text = service.myDev
}
The problem is: data from Firebase fetched only after my label got values of myName and myDev.Thus, this values is nil.
this is not the best solution, but it should solve your problem.
struct MyStructure {
var name = ""
var dev = ""
init(with dictionary: [String: String]) {
if let name = dictionary["name"] {
self.name = name
}
if let dev = dictionary["dev"] {
self.dev = dev
}
}
}
class Service {
let databaseRef = FIRDatabase.database().reference()
func getData(completion: #escaping ((_ structure: MyStructure) -> Void)) {
databaseRef.child("Data").observeSingleEvent(of: .value, with: { snapshot in
if let value = snapshot.value as? [String: String] {
completion(MyStructure(with: value))
}
}) { (error) in
print(error.localizedDescription)
}
}
}
let service = Service()
override func viewDidLoad() {
super.viewDidLoad()
service.getData { myStructure in
DispatchQueue.main.async {
self.titleLabel.text = myStructure.name
self.devLabel.text = myStructure.dev
}
}
}
Of course it does. The getData() method makes an asynchronous call to the Firebase service. Meaning, the .observeSingleEvent method of firebase runs in a async queue as most code blocks that makes a service call. So the compiler reaches to getData() line, pushes the block to a different queue and continues to compile the next lines which are in the original queue. If you want those lines to run after you receive the response, you may add a closure to your getData() method as a parameter.
You can do something like this:
getData(completion: () -> Void) {
let databaseRef = FIRDatabase.database().reference()
databaseRef.child("Data").observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
let name = value?["name"] as? String ?? ""
self.myName = name
let dev = value?["dev"] as? String ?? ""
self.myDev = dev
completion()
}) { (error) in
print(error.localizedDescription)
}
}
override func viewDidLoad(){
super.viewDidLoad()
service.getData(completion: { Void in
configureLabel()
})
}

cannot use instance member 'ref' with the initializer error

I am facing this error all is well, I want to print the children of "12356" in console but i am getting this error i dont know why this is happening and here is the Firebase structure
and this is the xcode error
and here is the code
import UIKit
import Firebase
import FirebaseDatabase
class TestViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
var ref = FIRDatabase.database().reference()
let root = ref.child("Items").child("Flate").child("12356")
root.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
//print(snapshot)
if let myfRateA = snapshot.value["fRateA"] as? String {
print(myfRateA)
}
if let myfRateB = snapshot.value["fRateB"] as? String {
print(myfRateB)
}
})
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
You did not put your code into the right place, because it is outside any method or function. Put it inside a function's body like so:
func printChildren(){
let ref = FIRDatabase.database().reference()
let root = ref.child("Items").child("Flate").child("12356")
root.observeSingleEvent(of: .value, with: { snapshot in
if !snapshot.exists() { return }
//print(snapshot)
let myfRateA = (snapshot.value as? NSDictionary)?["fRateA"] as? String ?? ""
print(myfRateA)
let myfRateB = (snapshot.value as? NSDictionary)?["fRateB"] as? String ?? ""
print(myfRateB)
})
}
And call it in viewDidLoad() like so:
override func viewDidLoad() {
super.viewDidLoad()
self.printChildren()
}