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
Related
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()
})
}
}
I am making an application which makes a lot of requests from an API. So I don't want to copy and past the code over and over. I was wondering how I can reuse my code in a some more efficient way? Maybe with extensions?
This is my code know:
func apiRequest() {
let config = URLSessionConfiguration.default
let username = "****"
let password = "****"
let loginString = String(format: "%#:%#", username, password)
let userPasswordData = loginString.data(using: String.Encoding.utf8)
let base64EncodedCredential = userPasswordData?.base64EncodedString()
let authString = "Basic " + (base64EncodedCredential)!
print(authString)
config.httpAdditionalHeaders = ["Authorization" : authString]
let session = URLSession(configuration: config)
var running = false
let urlProjects = NSURL(string: "https://start.jamespro.nl/v4/api/json/projects/?limit=10")
let task = session.dataTask(with: urlProjects! as URL) {
( data, response, error) in
if let taskHeader = response as? HTTPURLResponse {
print(taskHeader.statusCode)
}
if error != nil {
print("There is an error!!!")
print(error)
} else {
if let content = data {
do {
let dictionary = try JSONSerialization.jsonObject(with: content) as! [String:Any]
print(dictionary)
if let items = dictionary["items"] as? [[String:Any]] {
for item in items {
if let description = item["Description"] as? String {
self.projectNaam.append(description)
}
if let id = item["Id"] as? String {
self.projectId.append(id)
}
if let companyId = item["CompanyId"] as? String {
self.companyId.append(companyId)
}
}
}
self.apiRequestCompani()
}
catch {
print("Error: Could not get any data")
}
}
}
running = false
}
running = true
task.resume()
while running {
print("waiting...")
sleep(1)
}
}
Yes, you can use Extensions to create a BaseViewController and extend that where you want to use your code over and over again. Then you should abstract all dynamic data over input parameters to that method.
import UIKit
class BaseViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
func getApiRequest (Parameters) {
//API Request
}
And then in your view controller you just extend BaseViewController
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
//Call method in baseviewcontroller
getApiRequest(parameters)
//Call method in self
self.getApiRequest(parameters)
}
override func getApiRequest(Parameters) {
//IF you need to override default configuration
}
So I don't want to copy and past the code over and over.
Absolutely right, no one aiming to get duplicated code; That's the issue of massive view controller. This issue appears since the view controller layer in your application handles most of the responsibilities, such as: getting data from the network, how data should be represented, deliver the formatted data to the view layer, etc...
There are many approaches for solving such an issue (using an appropriate architectural pattern for your application), for simplicity, I would recommend to apply the MVC-N (or MVCNetworking) approach into your app, it is almost the same usual MVC, with a separated files (managers), represent a new layer for handling -for instance- the integration with the external APIs.
Applying the MVN-N should not be that complex, nevertheless it needs to be described well (which might be too abroad to be descried in the answer), I would suggest to check the above mentioned apple example, also watching this video should be useful.
This is my JSON data
{
"Number":"ID001",
"Password":"1111",
"Email":"email#gmail.com"
}
Currently i'm using SwiftyJSON to print specific JSON output in X Code. So far i managed to print email value specifically. But i'm not sure how to display email value in UILabel emailLbl.
The code as below.
import UIKit
import SwiftyJSON
class ViewController: UIViewController {
#IBOutlet var emailLbl: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let url = URL(string: "http://localhost/get.php")
let task = URLSession.shared.dataTask(with: url!) {
(data, response, error) in
if error != nil {
print("Error")
}
else {
guard let data = data else {
print("data was nill ?")
return
}
let json = JSON(data: data)
print(json["email"].string!)
}
}
task.resume()
}
}
Does Anyone have any idea how ?
Thanks.
Try this:
let json = JSON(data: data)
print(json["email"].string!)
self.emailLbl.text = json["email"].string!
I am fairly new to Swift but I have a NSTableView that is not displaying any of my data. My JSON data is being printed in the console perfectly and I thought that my cellView would display in my textField all my values but I get nothing back. I have my Table set up to where my 'Table Cell View' has an identifier of 'cell' so I believe they are linked correctly. I am not receiving any errors in the console but my data is still not displaying. Any help would be greatly appreciated.
import Cocoa
class ViewController: NSViewController, NSTableViewDataSource, NSTableViewDelegate {
#IBOutlet var tableView: NSTableView!
var values: NSArray = []
override func viewDidLoad() {
super.viewDidLoad()
get();
}
override var representedObject: Any? {
didSet {
}
}
func get(){
let url = NSURL(string: "http://myurl")
let data = NSData(contentsOf: url as! URL);
values = try! JSONSerialization.jsonObject(with: data! as Data,options: JSONSerialization.ReadingOptions.mutableContainers) as! NSArray
tableView.reloadData();
print(values);
}
func numberOfRows(in tableView: NSTableView) -> Int {
return self.values.count;
}
private func tableView(tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
let cellView = tableView.make(withIdentifier: "cell", owner: self) as! NSTableCellView
cellView.textField!.stringValue = self.values.object(at: row) as! String
return cellView
}
did you make sure your table view knows it's delegate and data source?, if not, add this to viewDidLoad
self.tableView.delegate = self
self.tableView.dataSource = self
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
}
}
}
}