How to properly remove func from ViewController swift - json

I'm quite tired because i feel there is simple solution but i can't find it for days! I'm trying to remove parseJSON() from ViewController so it looks better but it's not working when it's removed, yet it still parsing data. Inside ViewController this func works absolutely fine and showing tableView with parsed data. I tried to add reloadData() and it didn't help
https://imgur.com/a/DrHuUMK - screenshots
class MainViewController: UIViewController {
private var companyModel: CompanyModel?
private lazy var employeesTable: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
parseJSON()
view.addSubview(employeesTable)
employeesTable.frame = view.bounds
employeesTable.delegate = self
employeesTable.dataSource = self
}
private func parseJSON() {
guard let url = URL(string: "https://run.mocky.io/v3/1d1cb4ec-73db-4762-8c4b-0b8aa3cecd4c") else { return }
do {
let jsonData = try Data(contentsOf: url)
companyModel = try JSONDecoder().decode(CompanyModel.self, from: jsonData)
} catch {
print(error)
}
}
It's working as you can see on second screenshot
import Foundation
class NetworkManager {
var companyModel: CompanyModel?
func parseJSON() {
guard let url = URL(string: "https://run.mocky.io/v3/1d1cb4ec-73db-4762-8c4b-0b8aa3cecd4c") else { return }
do {
let jsonData = try Data(contentsOf: url)
companyModel = try JSONDecoder().decode(CompanyModel.self, from: jsonData)
} catch {
print(error)
}
}
}
import UIKit
class MainViewController: UIViewController {
var networkManager = NetworkManager()
private var companyModel: CompanyModel?
private lazy var employeesTable: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
networkManager.parseJSON()
view.addSubview(employeesTable)
employeesTable.frame = view.bounds
employeesTable.delegate = self
employeesTable.dataSource = self
}
}
but this one is not working, what am i doing wrong?

Your code has two critical problems
First of all, I see you have two companyModel : first one in class NetworkManager and second in class MainViewController and both have no connection which each others. The reason table view shows data when you parse json in MainViewController because it connects to the companyModel in MainViewController. You must remove one of them.
Second, when you parse json from URL, you should make an asynchronous function for parse Json in NetworkManager. Sometimes getting data from server may takes long time.
Code will be like this
class NetworkManager {
func parseJSON(completion: #escaping(_ companyModel: CompanyModel?) -> Void) {
guard let url = URL(string: "https://run.mocky.io/v3/1d1cb4ec-73db-4762-8c4b-0b8aa3cecd4c") else { return }
do {
let jsonData = try Data(contentsOf: url)
let companyModel = try JSONDecoder().decode(CompanyModel.self, from: jsonData)
// return data parse from server
completion(companyModel)
} catch {
print(error)
}
}
}
class MainViewController: UIViewController {
var networkManager = NetworkManager()
private var companyModel: CompanyModel?
private lazy var employeesTable: UITableView = {
let table = UITableView(frame: .zero, style: .grouped)
table.register(UITableViewCell.self, forCellReuseIdentifier: "cell")
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(employeesTable)
employeesTable.frame = view.bounds
employeesTable.delegate = self
employeesTable.dataSource = self
// add this line
self.initTableViewData()
}
func initTableViewData() {
networkManager.parseJSON(completion: {
companyModel in
self.companyModel = companyModel
self.employeesTable.reloadData()
})
}
}

Related

Swift parsing json, and show to the user issue

I am parsing a JSON on my own, after seeing a couple of tutorials, I try to adapt an OMBD API, but Xcode is throwing me this error Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee7ca9f68), how do I debug this, below is what I've done so far.
import UIKit
import SnapKit
class ViewController: UIViewController, MovieManagerDelegate {
lazy var titleLabel: UILabel = {
UILabel()
}()
var movieManager = MovieManager()
override func viewDidLoad() {
super.viewDidLoad()
movieManager.delegate = self
movieManager.performMovieRequest(urlRequest: movieManager.movieUrl)
viewHierarchy()
constraitsMaker()
additionalComponents()
}
func viewHierarchy() {
view.addSubview(titleLabel)
}
func constraitsMaker() {
titleLabel.snp.makeConstraints { (maker) in
maker.center.leading.trailing.equalToSuperview()
}
}
func additionalComponents() {
titleLabel.textColor = .black
}
func didUpdateTitle(movie: MovieModel) {
DispatchQueue.main.async {
self.titleLabel.text = movie.movieTitle
}
}
}
import Foundation
protocol MovieManagerDelegate {
func didUpdateTitle(movie: MovieModel)
}
struct MovieManager {
let viewController = ViewController()
let movieUrl = "https://www.omdbapi.com/?i=tt3896198&apikey=b6531970"
let posterUrl = "https://m.media-amazon.com/images/M/MV5BNjM0NTc0NzItM2FlYS00YzEwLWE0YmUtNTA2ZWIzODc2OTgxXkEyXkFqcGdeQXVyNTgwNzIyNzg#._V1_SX300.jpg"
var delegate: MovieManagerDelegate?
func performMovieRequest(urlRequest: String) {
if let url = URL(string: movieUrl) {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error != nil {
print(error!)
return
}
if let safeData = data {
let movie = self.parseJSON(movieData: safeData)
self.delegate?.didUpdateTitle(movie: movie!)
}
}
task.resume()
}
}
//parse json function
func parseJSON(movieData: Data) -> MovieModel? {// with data as a parameter
let decoder = JSONDecoder()
//do try catch to handle errors of decoded json
do {
let decodedData = try decoder.decode(MovieData.self, from: movieData)//call the data for decode json. movie data comes from the parameter of the function, that is subclassed as data object
let title = decodedData.Title
let movie = MovieModel(movieTitle: title)
return movie
} catch {
print(error)
return nil
}
}
import Foundation
struct MovieData: Codable {
let Title: String
let Year: String
let Rated: String
let Writer: String
let Released: String
let Runtime: String
let Genre: String
let Director: String
let Actors: String
let Plot: String
let Language: String
let Country: String
let Awards: String
let Poster: URL
let Ratings: [Ratings]
let Metascore: String
let `Type`: String
let DVD: String
let BoxOffice: String
let Production: String
}
struct Ratings: Codable {
let Source: String
let Value: String
}
import Foundation
struct MovieModel {
let movieTitle: String
}
MovieManager makes an asynchronous call when downloading data meaning the code after the call to performMovieRequest is executed before the data is downloaded and the label has been initialised. It should work fine to move the calls in viewDidLoad to the delegate method
func didUpdateTitle(movie: MovieModel) {
viewHierarchy()
constraitsMaker()
additionalComponents()
DispatchQueue.main.async {
self.titleLabel.text = movie.movieTitle
}
}
If you are calling the delegate method after that as well you might want to have a boolean property to verify the 3 methods from viewDidLoad doesn't get called again

Share JSON Data in TabBarController to view controllers

I am using a tabbarcontroller to show 3 xib's. I would like to decode JSON data in the UITabBarController subclass, and then share the data with the view controllers (as I understand that is the preferred way to do this). I had already successfully accomplished this individually in each view controller, where the same JSON data was getting decoded separately 3 times, but I am now trying to make the process more efficient by only dealing with JSON once.
I am currently getting the following error
"Thread 1: EXC_BAD_ACCESS (code=2, address=0x7ffee7ab7d98)".
Below is the code I am currently using. I'm mostly only including the code for the first view controller, but it is the same for the others
Here is one of the view controllers. Any help would be appreciated, thank you!
class FirstCollectionViewController: UIViewController {
var tbvc = CustomTabBar()
var statisticsData = [Model]()
let firstCellIdentifier = "FirstCellIdentifier"
#IBOutlet weak var FirstCollectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
FirstCollectionView.delegate = self
FirstCollectionView.dataSource = self
FirstCollectionView.register(UINib(nibName: "FirstCollectionViewCell", bundle: nil),forCellWithReuseIdentifier: firstCellIdentifier)
}
}
Here is the subclasses UITabBarController
import UIKit
class CustomTabBar: UITabBarController {
let website = "https:......."
var statisticsData = [Model]()
override func viewDidLoad() {
super.viewDidLoad()
let firstTab = FirstCollectionViewController(nibName: "FirstCollectionViewController", bundle: nil)
let secondTab = SecondCollectionViewController(nibName: "SecondCollectionViewController", bundle: nil)
let thirdTab = ThirdCollectionViewController(nibName: "ThirdCollectionViewController", bundle: nil)
viewControllers = [firstTab, secondTab, thirdTab]
downloadJSON(website: website) {
firstTab.statisticsData = self.statisticsData
secondTab.statisticsData = self.statisticsData
thirdTab.statisticsData = self.statisticsData
firstTab.FirstCollectionView.reloadData()
secondTab.SecondCollectionView.reloadData()
thirdTab.ThirdCollectionView.reloadData()
}
}
func downloadJSON(website:String, completed:#escaping ()->()){
guard let qurl = URL(string: website) else { return }
URLSession.shared.dataTask(with: qurl) { (data, response, error) in
if error == nil {
do{
self.statisticsData = try JSONDecoder().decode([Model].self, from: data!)
DispatchQueue.main.async{
completed()
}
} catch {
print("JSON Error")
}}
}.resume()
}
}
Once the data is loaded, you should assign the data to the viewControllers that are added in the tabBarController's Child list as below,
downloadJSON(website: website) {
firstTab.statisticsData = self.statisticsData
secondTab.statisticsData = self.statisticsData
thirdTab.statisticsData = self.statisticsData
firstTab.FirstCollectionView.reloadData()
secondTab.SecondCollectionView.reloadData()
thirdTab.ThirdCollectionView.reloadData()
}
You can also remove the below lines from viewDidLoad of FirstCollectionViewController, SecondCollectionViewController and ThirdCollectionViewController
tbvc = tabBarController as! CustomTabBar
statisticsData = tbvc.statisticsData

How to work with Core Data saving JSON response,Show data when internet is offline in Swift 3?

I have already parsed JSON and showing in tableView which is working fine. Now my question is how will i save data offline and show when internet is not available offline using Core Data. I am working in Swift 3. If anyone can help me with screenshot it will be great help.
Below is my Code for fetching json and showing on tableView :
import UIKit
import SystemConfiguration
struct CellData {
var name:String
var address:String
public init(name:String,address:String){
self.name = name
self.address = address
}
}
///ViewController
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate {
#IBOutlet weak var tableViewData: UITableView!
var arrayData = [CellData]()
override func viewDidLoad() {
super.viewDidLoad()
if Reachability.isConnectedToNetwork(){
print("Internet Connection Available!")
fetchServerData()
}else{
let alert = UIAlertController(title: "No Internet connection", message: "Please ensure you are connected to the Internet", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
print("Internet Connection not Available!")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as! MyCellData
cell.lblTop.text = "😀\(arrayData[indexPath.row].name)"
cell.lblBottom.text = arrayData[indexPath.row].address
return cell
}
func fetchServerData(){
let prs = [
"author_id": "1780",
"get_deals_author": "1" as String
]
Service.StartWithoutLoading(prs as [String : AnyObject]?, onCompletion: { result in
let json = result as? NSDictionary
if let data = json as? [String:Any]{
if let err = data["status"] as? String, err == "success"{
if let data = data["result"] as? [Any]{
var arrayData = [CellData]()
for sectionObj in data{
if let sectionObjVal = sectionObj as? [String:Any]{
if let name_deal = sectionObjVal["name"] as? String{
if let address_deal = sectionObjVal["address"] as? String{
let dataValue = CellData.init(name: name_deal, address: address_deal)
arrayData.append(dataValue)
}
}
}
}
DispatchQueue.main.async { () -> Void in
self.arrayData.removeAll()
self.arrayData = arrayData
self.tableViewData.reloadData()
}
}
}
}
})
}
}
For Core Data, you need to create the entities you need in CoreData model .xcdatamodeld. Click on Add Entity and name your entity. Then add attributes which you require to save.
You can see this link on how to create the entities and attributes. After creating everything, we can write a CoreDataStack and a manager class or we can directly use the code pre-written in AppDelegate when we check on Core Data when creating a project. I'll here use the CoreDataStack class.
Here is the class
import Foundation
import CoreData
class CoreDataStack: NSObject {
static let moduleName = "YourProject"
static let shared = CoreDataStack()
private override init() {
super.init()
_ = self.persistentContainer
}
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.save()
} catch {
let nserror = error as NSError
fatalError("Unresolved error \(nserror), \(nserror.userInfo)")
}
}
}
lazy var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: CoreDataStack.moduleName)
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
print("Coordinator URL - \(storeDescription)")
})
return container
}()
}
Now we can make a manager class to insert the data. Let's say your entity is Person and its attributes are name and address
Here is the CoreDataManager class to insert, update, fetch data.
import UIKit
import CoreData
class CoreDataManager: NSObject {
class func addRecord(object:[String:Any]) {
let person = NSEntityDescription.insertNewObject(forEntityName: "Person", into: CoreDataStack.shared.persistentContainer.viewContext) as! Person
person.name = object["name"] as? String
person.address = object["address"] as? String
CoreDataStack.shared.saveContext()
}
class func getRecords() -> [Person]? {
let request:NSFetchRequest<Person> = Person.fetchRequest()
do {
let results = try CoreDataStack.shared.persistentContainer.viewContext.fetch(request)
return results
} catch {
print(error.localizedDescription)
}
return nil
}
}
You can call addRecord method in your ViewController class and it will save your data. I recommend that you pass the complete array and then add in core data and finally call saveContext().
Finally you can use getRecords to get all records.

Alamofire download from JSON API

I'm trying to set text on a label from api, but it seems that the function doesn't even get called. Please refer to the snippet below. Is there anything wrong with it?
EDIT: typealias DownloadComplete = () -> ()
var date: String = ""
override func viewDidLoad() {
super.viewDidLoad()
timeLbl.text = date
// Do any additional setup after loading the view.
}
func downloadTimeData(completed: #escaping DownloadComplete) {
//Downloading forecast weather data for TableView
Alamofire.request(APIURL).responseJSON { response in
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
if let currentDate = dict["fulldate"] as? String {
self.date = currentDate
print(self.date)
print("xxx")
}
}
completed()
}
}
I figured it out with simpler and easier way, through the alamofire documetation.
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(APIURL).responseJSON { response in
print(response.result) // result of response serialization
let result = response.result
if let dict = result.value as? Dictionary<String, AnyObject> {
let currentDate = dict["fulldate"] as? String
self.timeLbl.text = currentDate
}
}
}
In the code you posted you are not calling downloadTimeData(completed:) anywhere.
You can do that in viewDidAppear(_:) for example:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
downloadTimeData {
// The request has completed
timeLbl.text = date
}
}
Note that you may need to change the call slightly, depending on how DownloadComplete is defined.
You are setting timeLbl.text immediately on page load in viewDidLoad, but you haven't told the app to do anything else.
You have to move downloadTimeData to viewDidLoad, and in the completion, set 'timeLbl.text = date'
You have to set some sort of text place holder or loader while your call is being made, because you can't guarantee that it is instant.
Are we setting one label? Or a whole tableview of labels?
I changed some syntax to be "swiftier"
var date = ""
override func viewDidLoad() {
super.viewDidLoad()
//call downloadTimeData here
downloadTimeData() {
//once we are in completion, this means internet call finished, so set label now
self.timeLbl.text = date
}
}
func downloadTimeData(completed: #escaping DownloadComplete) {
//Downloading forecast weather data for TableView
Alamofire.request(APIURL).responseJSON { response in
guard let dict = response.result.value as? [String: AnyObject], let currentDate = dict["full date"] as? String else {
//handle error here if response fails to give you good data
completed()
return
}
self.date = currentDate
print(self.date)
print("xxx")
completed()
}
}

Passing JSON data to a label in Swift

I'm new to coding and have a (hopefully easy) question.
I am trying to display JSON data as a label using swift. I'm not getting any errors but nothing is showing up using the following code:
let task = NSURLSession.sharedSession().dataTaskWithURL(url) { (data, response, ErrorType) -> Void in
if let urlContent = data {
do {
let jsonResult = try NSJSONSerialization.JSONObjectWithData(urlContent, options: NSJSONReadingOptions.MutableContainers)
print(jsonResult)
let text = jsonResult as? String
self.textLabel.text = text
} catch {
print("JSON serialization failed")
}
}
}
task.resume()
Thanks!
Just update your label into main thread this way:
dispatch_async(dispatch_get_main_queue()) {
self.textLabel.text = text
}
UPDATE:
You can use SwiftyJSON for that.
And below is your working code with that library:
import UIKit
class ViewController: UIViewController {
var dict = NSDictionary()
#IBOutlet weak var authorLbl: UILabel!
#IBOutlet weak var quoteLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "http://api.theysaidso.com/qod.json"
if let url = NSURL(string: urlString) {
if let data = try? NSData(contentsOfURL: url, options: []) {
let json = JSON(data: data)
print(json)
let auther = json["contents"]["quotes"][0]["author"].stringValue
let quote = json["contents"]["quotes"][0]["quote"].stringValue
authorLbl.text = auther
quoteLbl.text = quote
}
}
}
}