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
Related
I am able to parse JSON and adding cells in Collectionview.. but if i move from this Viewcontroller and coming to viewcontroller then collectionview is not showing.. but added data in JSON
code for adding collectionview and JSON parsing:
class ImageItemModel{
var title: String?
var profileImage: UIImage?
var pic_id: Double?
init(title: String?, imgTitle: UIImage?, pic_id: Double?) {
self.title = title
self.profileImage = imgTitle
self.pic_id = pic_id
}
}
class EditProfileImageViewController: UIViewController {
#IBOutlet weak var titleTextfield: UITextField!
private var imageProfile : UIImage?
private var imagePicker : EasyImagePicker?
#IBOutlet weak var collectionView: UICollectionView!
var arrImageItems = [ImageItemModel]()
#IBAction func imgtitleSaveBtn(_ sender: Any) {
postServiceCall()
}
fileprivate func postServiceCall(){
if titleTextfield.text?.trim() == ""{
return self.view.makeToast("please add service title")
}
let parameters = ["image_title" : titleTextfield.text?.trim() ?? ""]
APIReqeustManager.sharedInstance.uploadMultipartFormData(param: parameters, url: CommonUrl.edit_profile_images, image: imageProfile, fileName: "image", vc: self, isHeaderNeeded: true) {(responseData) in
print("edit profile result \(responseData)")
if let result = responseData.dict?["result"] as? NSDictionary{
let success = result["status"] as? [String : Any]
let message = success?["message"] as? String
if message == "Success"{
let image = result["image"] as? [String : Any]
let picId = image?["id"]
self.arrImageItems.append(ImageItemModel(title: self.titleTextfield.text, imgTitle: self.imageProfile, pic_id: picId as! Double))
self.collectionView.reloadData()
}
else{
self.view.makeToast(CommonMessages.somethingWentWrong)
}
}
}
}
extension EditProfileImageViewController : UICollectionViewDelegate,UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrImageItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as! ImageCollectionViewCell
cell.imgView.image = arrImageItems[indexPath.item].profileImage
cell.lblTitle.text = arrImageItems[indexPath.row].title
cell.deleteButton.tag = indexPath.row
cell.deleteButton.addTarget(self, action: #selector(deleteService(sender:)), for: UIControl.Event.touchUpInside)
return cell
}
}
with the above code i am able to add collectionview cells and able to store data in JSON but.. if i move from this viewcontroller and coming back to this viewcontroller then collectionview is not showing, why? whats wrong? please do help me with code.. i got stuck here from long time.
There are couple of issues that you should fix for this to work properly. I will give you reason for each.-
You are loading your data with the postServiceCall() method which has an asynchronous network call. There is no way to know when the controller is done fetching the data to the arrImageItems array. So, you should have used a completion handler.
Now you are updating the collectionView within the asynchronous dataTask which a background thread. BIG mistake. Whenever you have any UI related task, you do it under the main thread. So, you could refactor the APIReqeustManager.sharedInstance.uploadMultipartFormData() part of your code following way-
APIReqeustManager.sharedInstance.uploadMultipartFormData(param: parameters, url: CommonUrl.edit_profile_images, image: imageProfile, fileName: "image", vc: self, isHeaderNeeded: true) {(responseData) in
print("edit profile result \(responseData)")
if let result = responseData.dict?["result"] as? NSDictionary{
let success = result["status"] as? [String : Any]
let message = success?["message"] as? String
if message == "Success"{
let image = result["image"] as? [String : Any]
let picId = image?["id"]
self.arrImageItems.append(ImageItemModel(title: self.titleTextfield.text, imgTitle: self.imageProfile, pic_id: picId as! Double))
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
else{
DispatchQueue.main.async {
self.view.makeToast(CommonMessages.somethingWentWrong)
}
}
}
}
Now unless you want your viewcontroller to show the data in your collectionView only when the action, imgtitleSaveBtn(_:) is triggered, you need to get data everytime, when your view controller appeared on screen. To fix that issue, you should get the data in the viewWillAppear(_:) method like-
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
postServiceCall()
}
Now, the above two edits should fix your issue depending on how you want to load your collectionview but your code is breaking quite some coding standards. Coding standards sounds like a clique but trust me you want to follow those if you ever want to update the capability of your app without breaking it. The following is just some hints-
Whenever you are in an asynchronous call, you should consider calling a completion handler for returning your data.
Should look into your methods, you are dangerously breaking the single responsibility principal.
In more than one place, you force unwrapped. Bad idea. You need your system to have a fail safe rather than just crashing on you.
Update 2:
Updates with a design pattern:
Compartmentalise your code in MVC pattern. Put the ImageItemModel class in its own file. See the image below to understand the design-
Customize the collectionViewCell within the ImageCollectionViewCell. Let's assume your custom cell has only the outlets.
class ImageCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imgView: UIImageView!
#IBOutlet weak var lblTitle: UILabel!
#IBOutlet weak var deleteButton: UIButton!{
didSet{
deleteButton.addTarget(self, action: #selector(deleteService(_:)), for: .touchUpInside)
}
}
// however this could easily be done with IBAction
#objc func deleteService(_ sender: UIButton){
}
}
Update the postServiceCall and return the data to your controller with a completion handler, means when the postServiceCall is done executing, an array of images or an empty array should be returned based on success or failure. Then the controller can decide what to do with the data, in your case update UI. with couple of refactoring, here is the updated controller code.
import UIKit
import EasyImagePicker
class EditProfileImageViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!{ //for troubleshooting
didSet{ //purpose, do it from code
collectionView.delegate = self
collectionView.dataSource = self
}
}
#IBOutlet weak var titleTextfield: UITextField!
private var imageProfile : UIImage?
private var imagePicker : EasyImagePicker? // you never used this var.
var arrImageItems = [ImageItemModel]()
// any time a view controller appears on screen this method gets called.
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
reloadMyCollectionView()
}
#IBAction func imgtitleSaveBtn(_ sender: Any) {
reloadMyCollectionView()
}
fileprivate func reloadMyCollectionView(){
postServiceCall{ images in
self.arrImageItems = images
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
}
// look into escaping closures to understand, why you need it here
fileprivate func postServiceCall(completed: #escaping(_ images: [ImageItemModel])->Void){
// don't force unwrap, get optional values safely with guard let
guard let titleText = titleTextfield.text, titleText == "" else{
return
}
let parameters = ["image_title" : titleText]
APIReqeustManager.sharedInstance.uploadMultipartFormData(param: parameters, url: CommonUrl.edit_profile_images, image: imageProfile, fileName: "image", vc: self, isHeaderNeeded: true) {(responseData) in
print("edit profile result \(responseData)")
//capture the data in local scope and return that array with a completion handler
var imageItems = []
if let result = responseData.dict?["result"] as? NSDictionary{
let success = result["status"] as? [String : Any]
let message = success?["message"] as? String
if message == "Success"{
let image = result["image"] as? [String : Any]
let picId = image?["id"]
imageItems.append(ImageItemModel(title: self.titleTextfield.text, imgTitle: self.imageProfile, pic_id: picId as! Double))
}
}
self.completed(imageItems) // if there is nothing in result,
//imageItems will be empty, otherwise it will have imageItemModel data
}
}
}
extension EditProfileImageViewController : UICollectionViewDelegate,UICollectionViewDataSource{
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arrImageItems.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "ImageCollectionViewCell", for: indexPath) as? ImageCollectionViewCell{
cell.imgView.image = arrImageItems[indexPath.item].profileImage
cell.lblTitle.text = arrImageItems[indexPath.row].title
cell.deleteButton.tag = indexPath.row
//cell.deleteButton.addTarget(self, action: #selector(deleteService(sender:)), for: UIControl.Event.touchUpInside)
return cell
}
else{
return UICollectionViewCell()
}
}
}
Notice postServiceCall and cellForItemAt methods.
If you still have the same issue then you need to show your whole code to get any further help.
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.
i started to learn IOS development using swift 3.0.i built a simple app to call web api to query data from server database. i can get the json data and parsed it into string array. the App can print the array, but it cannot show in the tableview. it confused me several days and i searched some examples and answers on internet but still couldn't work out it.
My codes as below:
class LocationTableViewController: UITableViewController {
var names: [String] = []
override func viewDidLoad() {
super.viewDidLoad()
//ββββββββββββββββββββββββββget the data from web api and using json parsingββββββββββββββββββββββββ
let config = URLSessionConfiguration.default // Session Configuration
let session = URLSession(configuration: config) // Load configuration into Session
let url = URL(string: "http://XXXXXXX/api/mylocations")!
let task = session.dataTask(with: url, completionHandler: {
(data, response, error) in
if error != nil {
print(error!.localizedDescription)
} else {
do {
var jsonResult: NSMutableArray = NSMutableArray()
let jsonArray = try JSONSerialization.jsonObject(with: data!, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
jsonResult = jsonArray.mutableCopy() as! NSMutableArray
var jsonElement: NSDictionary = NSDictionary()
for i in 0..<jsonResult.count {
jsonElement = jsonResult[i] as! NSDictionary
if let name = jsonElement["Name"] as? String
{
// print(id)
// print(name)
// print(address)
// print(latitude)
// print(longitude)
// print("-------")
self.names.append(name)
}
// self.tableView.reloadData()
// print(self.names)
}
print(self.names)
// can print the string array data like [βname1β,βname2β,βname3β]
} catch {
print("error in JSONSerialization")
}
}
})
task.resume()
//-------------- βββ result is [] it seems the above code didn't put the string array to names.ββββββββββββββ
print(self.names)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count;
}
internal override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cellIdentifier = "cell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for:
indexPath as IndexPath) as UITableViewCell
// Configure the cell...
cell.textLabel?.text = names[indexPath.row]
return cell
}
}
Can anyone help me have a look?
Put self.tableView.reloadData() after print print(self.names).
At the point where you have commented...
result is [] it seems the above code didn't put the string array to
names
This line of code is being executed before the data has been downloaded within the completion handler, so we wouldn't expect to see anything here. You will note that it is working on the other print that you have within the completion handler.
The tableView.reloadData() at the end of the completion handler should be working.
Are you sure that you have the delegates set up correctly for the tableView? What do you see if you comment out the download task, and simply set
names = ["Tom", "Dick", "Harry"]
within viewDidLoad ? If that doesn't work, it's a problem with the delegates.
in the moment we're programming a Swift App for iOS in which we want to get data of our JSON Website (MySql database) into the TableViewCell. The problem is by appending the text values of the strings for the label in the cell. Swift can import the JSON values into the name variable but I cant assign it to the text array for the cells. I havent no syntax errors, but the data[0] Variable print sth. as "123". Why it is 123? The test Value is "Test". I don't now where the problem by appending the value to the array is, that the result is 123 after that. Please help.
Here is the sourcecode:
class listViewViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var data:[String?] = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let myUrl = URL(string: "");//Empty link for this question
var request = URLRequest(url:myUrl!)
request.httpMethod = "POST"
let postString = "lid=1";
request.httpBody = postString.data(using: String.Encoding.utf8);
let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
if error != nil {
print("error=\(error)")
return
}
print("response = \(response!)")
do {
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let Name = parseJSON["Name"] as? String
print("\(Name)")//Test
self.data.append(Name!)
print("\(data![0])" as String)//123
}
} catch {
print(error)
}
}
task.resume()
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! listViewTableViewCell
print("\(data[indexPath.row])")
let dataCell = data[indexPath.row]
cell.listViewCell.text = dataCell
return cell
}
}
this because your array properties and data callback block parameter have the same name "data". in your code you user print("(data![0])" as String) instead of print("(self.data![0])" as String) => you have to add self.
then you can optimise your code like this (it's optional : it's just like a code review ;) )
try to do this
- change your array type to String like this
var data = [String]()
- webService callback change your code like this :
if let parseJSON = json {
if let Name = parseJSON["Name"] as? String{
print("\(Name)")
self.data.append(Name)
print("\(self.data.last)")//123
}
}
When you append to your data array you use self.data but you then print from data which is the parameter to the inner function. You add and print from different arrays.
I'm new to iOS programming, so my question might not be complicated but I'm still struggling to find the best solution. Any help will be highly appreciated!
I'm trying to send GET request every time the user opens the app. I wrote the function loadMenu() that collects the data from a json file on the server and populates the table in the app.
The problem is that if I update the json file, it's not reflected in the app. If feels like the loadMenu() part of the code is just ignored.
Here's my code:
import UIKit
class TableViewControllerNew: UITableViewController {
var names = [String]()
var mealDescription = [String]()
var price = [Double]()
override func viewDidLoad() {
super.viewDidLoad()
loadMenu()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
var new = NSUserDefaults.standardUserDefaults().objectForKey("names") as! [String]
print("test: \(new.count)")
return new.count
}
func loadMenu() {
print("viewDidLoad works")
// Send HTTP GET
let myUrl = NSURL(string: "http://localhost/myPi/selection/wheyStationSelection.json");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "GET";
NSURLSession.sharedSession().dataTaskWithRequest(request, completionHandler: { (data:NSData?, response:NSURLResponse?, error:NSError?) -> Void in
dispatch_async(dispatch_get_main_queue()) {
do {
let json = try NSJSONSerialization.JSONObjectWithData(data!, options: .AllowFragments)
if let meals = json["meals"] as? [[String: AnyObject]] {
for meal in meals {
if let name = meal["name"] as? String {
self.names.append(name)
//NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1") as! [String]
//var new = NSUserDefaults.standardUserDefaults().objectForKey("test1") as? [String]
//print(new)
}
if let mealDescription = meal["mealDescription"] as? String {
self.mealDescription.append(mealDescription)
}
if let price = meal["price"] as? Double {
self.price.append(price)
}
}
}
} catch {
print("error serializing JSON: \(error)")
}
NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1") as! [String]
//print(self.names)
//print(self.mealDescription)
//print(self.price)
}
}).resume()
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdenifier = "MealTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdenifier, forIndexPath: indexPath) as! MealTableViewCell
var new = NSUserDefaults.standardUserDefaults().objectForKey("names") as! [String]
let name = new[indexPath.row]
//print(name)
cell.mealNameLabel.text = name
return cell
}
#david is right, if you place your loadMenu() method in viewDidAppear() it will be called each and every time your view appears. You can read more about the various lifecycle phases of a UIViewController here
One other thing. It is not clear to me whether your loadMenu() isn't called every time or whether you are just not seeing the updated content.
I can see that you are not reloading your table view when the JSON has been updated. Therefore your TableView don't know that any updates has occurred and will not render again and you won't see any updates.
Therefore, right after this line:
NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1")
You should tell your tableView to reload itself like so:
tableView.reloadData()
So you have:
NSUserDefaults.standardUserDefaults().setObject(self.names, forKey: "test1")
tableView.reloadData()
That should cause all your "render the TableView" methods to be called again, but this time with the new data and you should see the updated content.
Hope this helps you.
If you call loadMenu() in viewDidAppear(animated:) instead of viewDidLoad(), then it will be called every time you leave your app and reopen it.