Im trying to create a table which fills in its data from an online JSON file. I currently have the JSON file online and being called into the class and getting converted into an array, however when I try set my text as this array data nothing displays.
cell.textLabel?.text = "\(locationName[indexPath.row])"
However if I set the data in the array it works however I need the data to be from the JSON file.
var array: double = ["1", "2", "3"]
Here is my table class code with the array code. Any help would be great thanks
class SpotsDataClass: UITableViewController {
var locationName = [String]()
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "http://woamph.com/savedLocations.json")
.response { request, response, data, error in
if let data = data {
let json = JSON(data: data)
for locationLoop in json {
let usersJSON = locationLoop.1["user"].stringValue
self.locationName.append(usersJSON)
//let longitudeJSON = locationLoop.1["longitude"].doubleValue
//self.array2.append(longitudeJSON)
}
for i in 0 ..< self.locationName.count {
print("\(self.locationName[i])")
//print("\(self.array2[i])")
//self.locationNameLabel.text = "Location Name: \(self.array1[i])"
}
//self.titleLabel.text = "Spots Near You"
//self.locationNameLabel.text = "Location Name: \(spotData.locationName)"
//self.ratingLabel.text = "Rating: \(spotData.rating)"
//self.userLabel.text = "Posted By: \(spotData.user)"
//
}
}
}
//Mark: - UITableDataSource
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return locationName.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("spotTable", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel?.text = "\(locationName[indexPath.row])"
return cell
}
}
The Alamofire request executes asynchronously. You need to explicitly tell your table view to reload when the network request is done, otherwise you're populating your array from the results but the table view has already been loaded by the time your remote data has been loaded.
So, after
for locationLoop in json {
let usersJSON = locationLoop.1["user"].stringValue
self.locationName.append(usersJSON)
}
Add:
tableView.reloadData()
Related
In each row of a tableview there is a label and button. The label displays a quotes from an array. Users can tap the button to save the quote. Right now it works fine with UserDefaults, but I want to also save the information to Firebase. I can't seem to figure out how to get the quote to save to Firebase based on the heart/row that was tapped. I thought I could use IdexPath, but I can't seem to get it right. I'd like to save each quote as a unique value in Firebase and be able to delete it when the button is tapped again. However, I'm not so familiar with firebase.
I thought I could use IdexPath to determine which row was selected and then grab the label in that row to send to Firebase, but I can't seem to get it right. Instead I got an error preventing the code from running "Instance member 'row' cannot be used on type 'IndexPath'; did you mean to use a value of this type instead?"
import UIKit
import FirebaseDatabase
import FirebaseAuth
class QuotesMainViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var quotesTableView: UITableView!
struct Quote: Codable {
var label: String
var like: Bool = false // a priori, false
}
var quoteList: [Quote] = []
override func viewDidLoad() {
super.viewDidLoad()
quotesTableView.delegate = self
quotesTableView.dataSource = self
let defaults = UserDefaults.standard
if let data = defaults.data(forKey: "QuoteListKey") {
if let array = try? PropertyListDecoder().decode([Quote].self, from: data) {
quoteList = array
}
} else {
quoteList = [Quote(label: "Quote1"), Quote(label: "Quote2"), Quote(label: "Quote3")]
}
}
#IBAction func likeTapped(_ sender: UIButton) {
var ref: DatabaseReference?
ref = Database.database().reference()
quoteList[sender.tag].like.toggle() // update the dataSource ; sender.tag gives the row in the array
if quoteList[sender.tag].like {
sender.setImage(UIImage(named: "GreenHeart"), for: .normal) // You can change here or ask for a reloadData()
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quotes").setValue(quoteList[IndexPath.row].label)
if let data = try? PropertyListEncoder().encode(quoteList) {
UserDefaults.standard.set(data, forKey: "QuoteListKey")
}
} else {
sender.setImage(UIImage(named: "blankHeart"), for: .normal)
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return quoteList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = quotesTableView.dequeueReusableCell(withIdentifier: "cell") as! QuotesTableViewCell
cell.quoteLabel.text = quoteList[indexPath.row].label
cell.likeButton.tag = indexPath.row // Use tag to reference the cell, not to set true / false
cell.likeButton.setImage(UIImage(named: quoteList[indexPath.row].like ? "GreenHeart" : "blankHeart"), for: .normal)
return cell
}
}
I'm learning how to get data using Github API and making a simple app in swift. Tutorials I've been watching make an empty array and geting response function in the same controller file. However, I want my view controller to keep as small as possible, and I decided to create getting data function in another file. My question is how can I access the empty array in a different file after I fetch data. Maybe this explanation make you confused, so I put my code below.
This is my view controller file, and I want to populate the repositories array after fetch the data using an API call.
import UIKit
class RepositoryListVC: UIViewController {
var tableView = UITableView()
var repositories: [Repository] = []
override func viewDidLoad() {
super.viewDidLoad()
title = "AAAAAA"
configureTableView()
Service.fetchData()
}
func configureTableView() {
view.addSubview(tableView)
tableView.delegate = self
tableView.dataSource = self
tableView.rowHeight = 100
tableView.register(RepositoryCell.self, forCellReuseIdentifier: Cells.repositoryCell)
tableView.pin(to: view)
}
}
extension RepositoryListVC: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: Cells.repositoryCell) as! RepositoryCell
cell.set()
return cell
}
}
This is the file for fetching data using an API call. I want to populate the repository valuable in the view controller file above. I have successfully get data and parse JSON data, so I want to push Repository(...) to repository array, but I was not able to figure out how...
import UIKit
import SwiftyJSON
import Alamofire
struct Service {
static func fetchData() {
AF.request(API.gitHubEndpoint).responseJSON { (response) in
switch response.result {
case .success(let value):
let json = JSON(value)
let repositoryItemArr = json["items"].arrayValue
for item in repositoryItemArr {
Repository(userImageUrl: item["owner"]["avatar_url"].stringValue, userName: item["owner"]["user_name"].stringValue, repositoryName: item["owner"]["repository_name"].stringValue, starNum: item["owner"]["star_number"].intValue)
}
case .failure(let error):
print(error)
}
}
}
I am fairly new to Swift and I am having a few issues with getting understanding how to do what I want to do.
I am currently testing some stuff with json.
What I am trying to do is to append the data I get from my json data into an array. And when my array contains the data I wish to present it to my UICollectionView. I am assuming that I should be using an array.
import UIKit
import Foundation
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
#IBOutlet weak var collectionView: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func getData() {
let path = "http://myurl/test.json"
let url = URL(string: path)
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data: Data?, response: URLResponse?, error: Error?) in
let json = JSON(data: data!)
for result in json["dokumentstatus"]["dokutskott"]["utskott"].array! {
let punkter = result["punkt"].string!
print("punkt: \(punkt)")
let rubrik = result["rubrik"].string
print("rubrik: \(rubrik)")
let forslag = result["forslag"].string
print("förslag: \(forslag)")
}
}
task.resume()
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return //someArray.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
cell.customLabel.text = //Put the data form rubrik variable
cell.customTW.text = //Put the data from foreleg variable
return cell
}
}
the function getData() gets the correct json data I just need help understanding how to put this data to an array.
(Also, I know I probably shouldn't be getting the data in the ViewController, but this is only a test.)
import Foundation
class Information: NSObject {
var punkter: String?
var rubrik: String?
var forslag: String?
}
I'm thinking that maybe I should be using an array that looks something like this:var someArray = [Information]()
But I do not know how to then use my getData()
Or maybe I should be using three different arrays, one for each of my json variables.
Since you're still using a custom class (well done!, three different arrays are horrible) it's correct to declare a data source array like you suggested
var someArray = [Information]()
Most likely a struct is sufficient, I recommend to use non-optional strings
struct Information {
var punkter : String
var rubrik : String
var forslag : String
}
If the properties won't change you could even use let to make the properties constants.
To populate the array use the nil coalescing operator to check for nil,create the Information instance with the memberwise initializer and append the instance to the datasource array. Then reload the collection view on the main thread.
...
for result in json["dokumentstatus"]["dokutskott"]["utskott"].array! {
let punkter = result["punkt"].string ?? ""
let rubrik = result["rubrik"].string ?? ""
let forslag = result["forslag"].string ?? ""
let information = Information(punkter:punkter, rubrik:rubrik, forslag:forslag)
self.someArray.append(information)
}
DispatchQueue.main.async {
self.collectionView.reloadData()
}
...
Edit:
To display the data in cellForItemAtIndexPath use
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! CustomCollectionViewCell
let information = someArray[indexPath.row]
cell.customLabel.text = information.rubrik
cell.customTW.text = information.foreleg
return cell
}
I am trying to create an app that displays a table view based on an array of strings.
I have one view controller and a smaller content view within it. In the view controller there is a text field and a button that should save the written text in an array and display it in the table view controller. As well as embedding it in the content view.
I don't know how to save the written text and to add it in the array, perhaps using append.
How can I display the array in the table view and to save the array in NSUserDefaults?
EDIT:
Here's an image of the view controller and the content view. I want to insert one string in the text field (the one over the green button Save), then I tap the green button and the string I wrote is added in the array and displayed in a table view cell of the table view controller embed in the content view. At the same time, the text field return empty, but I already know how to clear it. Then, I can re-write texts in the text field and it should repeats the actions I just described.
A the moment isn't so important to save in NSUserDefaults.
Thanks for the help. :)
http://i.stack.imgur.com/z5uTc.png
EDIT 2:
MainVC
import UIKit
class mainVC: UIViewController {
#IBOutlet var txtField: UITextField!
var embTableVC: tableVC!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embededTableVC" {
embTableVC = segue.destinationViewController as! tableVC
}
}
#IBAction func Save() {
if let Text = txtField.text {
if txtField.text == "" {
myArray.append(Text)
let row = myArray.count-1
let indexPath = NSIndexPath(forRow: row, inSection: 0)
embTableVC.myTableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
txtField.text = ""
txtField.resignFirstResponder()
}
}
TableVC
import UIKit
var myArray = [String]()
class tableVC: UITableViewController {
#IBOutlet var myTableView: UITableView! {
didSet {
myTableView.dataSource = self
myTableView.delegate = self
}
}
override func viewDidLoad() {
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
myTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "customcell")
// Do any additional setup after loading the view, typically from a nib.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
myTableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCellWithIdentifier("customcell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = myArray[indexPath.item]
return cell
}
Thanks a lot :)
To store the information:
// Get the standardUserDefaults object, store your UITableView data array against a key, synchronize the defaults
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:arrayOfImage forKey:#"tableViewDataImage"];
[userDefaults setObject:arrayOfText forKey:#"tableViewDataText"];
[userDefaults synchronize];
To retrieve the information:
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSArray *arrayOfImages = [userDefaults objectForKey:#"tableViewDataImage"];
NSArray *arrayOfText = [userDefaults objectForKey:#"tableViewDataText"];
// Use 'yourArray' to repopulate your UITableView
On first load, check whether the result that comes back from NSUserDefaults is nil, if it is, you need to create your data, otherwise load the data from NSUserDefaults and your UITableView will maintain state.
In Swift, the following approach can be used:
let userDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(arrayOfImage, forKey:"tableViewDataImage")
userDefaults.setObject(setObject:arrayOfText, forKey:"tableViewDataText")
userDefaults.synchronize()
var arrayOfImages = userDefaults.objectForKey("tableViewDataImage")
var arrayOfText = userDefaults.objectForKey("tableViewDataText")
Hope this helps. You can also use a xcdatamodeld to save and retrieve data.
Here's a simple solution for storing the text in an array, a better option than NSUserDefaults if you might have a large number of Strings.
First, you will need to have an Array of Strings in the View Controller managing the Table View. Then, you will need a way to access that Array and edit it.
I would store a reference to the table view controller within the first view controller (with the container). To first set this reference, use the embed segue.
In your storyboard, the arrow connecting the first VC to the table VC is actually an embed segue that fires upon load of the container view. Click the segue, and in the attributes inspector in Xcode change the identifier to some String such as "embedTableVC".
Then we can set the reference in the first view controller. Here's some relevant code, assuming the view controller with the container has a class of MainViewController and the table view controller within the container has a class of TableViewController:
class MainViewController: UIViewController {
var embededTableVC: TableViewController!
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "embedTableVC" {
embededTableVC = segue.destinationViewController as! TableViewController
}
}
}
Once the main view controller loads, the container will be loaded, which will then cause the segue to run, which will cause prepareForSegue to be called. In this implementation, we are storing the table view controller in a property on our main view controller so we can access it. Since classes are reference types, this property will refer to the same object, not a copy.
Then, you get the text from the text field once the save button is pressed, and set it to the array in the table VC, like this:
#IBAction func save() {
if let text = textField.text {
if text != "" {
embededTableVC.valueArray.append(text)
// and if you want to go ahead and add it to the array from here instead of using delegation or notification observance
let row = embededTableVC.valueArray.count - 1
let indexPath = NSIndexPath(forRow: row, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
}
The if-let syntax ensures the text property of the text field is not nil.
I believe this is what you were looking for, but keep in mind that everything will be gone upon app relaunch, since we're not using NSUserDefaults or Core Data to store anything to the device's drive. You'll want a combination of the two approaches if you need persistence.
Edit:
As far as populating the table view with content from the array, you should consult Apple's Docs, as explaining it here would get pretty lengthy and the information may be found easily. Alternatively, you can check out this article to get an understanding for populating the table view, or see this question. You will need to implement numberOfSectionsInTableView, numberOfRowsInSection, and cellForRowAtIndexPath at a minimum for your table view.
Edit 2:
After reading your code, I'd say there are many things that probably ought to be changed eventually, but just to get it to work for now, the following changes need made for now:
Change this (from Save())…
if let Text = txtField.text {
if txtField.text == "" {
myArray.append(Text)
let row = myArray.count-1
let indexPath = NSIndexPath(forRow: row, inSection: 0)
embTableVC.myTableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
…to this
if let text = txtField?.text {
if text != "" { // Notice the two changes on this line
myArray.append(text)
let row = myArray.count - 1
let indexPath = NSIndexPath(forRow: row, inSection: 0)
embTableVC.myTableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
Change this (in cellForRowAtIndexPath)…
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCellWithIdentifier("customcell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = myArray[indexPath.item]
return cell
}
…to this
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myTableView.dequeueReusableCellWithIdentifier("customcell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = myArray[indexPath.row] // Notice `row`, not item`
return cell
}
If you're still getting a crash because a nil optional was unwrapped, which is highly possible, I need to know what line Xcode crashes on and I need to know what the error says.
import UIKit
class SecondViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
**var message = ["a","b"]
var toPass: String!**
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
// Do any additional setup after loading the view.
**message.append(toPass)**
}
#IBAction func SendButon(_ sender: UIButton) {
performSegue(withIdentifier: "segue2", sender: nil)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// tableView.reloadData()
return message.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UINib(nibName: "TableViewCell", bundle: nil),
forCellReuseIdentifier: "CellFor")
let cell = tableView.dequeueReusableCell(withIdentifier: "CellFor", for: indexPath) as! TableViewCell
cell.labelView.text = message[indexPath.row]
return cell
}
}
I have two ViewControllers. For the first ViewController, it displays my Array data on the table. I want to get the indexPath of the selected cell, and pass this data to another ViewController.
In my First ViewController
import UIKit
class ViewController: UIViewController, UITableViewDataSource {
var nameList = [NameManager]()
#IBOutlet weak var NameTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
NameTable.dataSource = self
GetData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func GetData(){
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.json-generator.com/api/json/get/bPfifKWNaq?indent=2")!)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let data = data{
do{
let resultJSON = try NSJSONSerialization.JSONObjectWithData(data, options: NSJSONReadingOptions())
let resultArray = resultJSON as? NSArray
for jsonObjectString in resultArray!{
let code = jsonObjectString["code"] as! String
let name = jsonObjectString["name"] as! String
let description = jsonObjectString["description"] as! String
self.nameList.append(NameManager(code: code, name: name, description: description))
}
self.nameList.count
dispatch_async(dispatch_get_main_queue(), {
self.NameTable.reloadData()
})
}catch _{
print("Received not-well-formatted JSON")
}
}
if let response = response {
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
let count = nameList.count
return count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let myCell = NameTable.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
myCell.textLabel?.text = nameList[indexPath.row].name
myCell.detailTextLabel?.text = nameList[indexPath.row].description
return myCell
}
In my Second ViewController, which is also another class, I want to capture the indexPath of what was selected. Using the index value, I search my Array and pass that particular object to the next class. I don't have codes for this as I don't know how it works.
You could create a property outside the scope of your class and then set that within the cellForRowAtIndexPath method. That property could be accessed in your second view controller. You’ll want to look at the tableview method didSelectRowAtIndexPath as that will be where you set your property to the cell that’s been selected.
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
appDelegate().index = indexPath.row
}
I’ve made a super simple project on github showing two ways of creating a property outside the scope of your view controller. One way is creating a variable within AppDelegate to be accessed via a singleton, the other is a Globals.swift file.
Hope this helps!
If you want to pass values to the controller which pops after tapping on the cell, using a singleton wouldn't be an elegant way. If you are using storyboards, then you have to use 'prepare for segue'. You implement the method in the class that handles the transfer and set all the properties in another view controller.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
if (segue.identifier == "SecondVC") {
// set properties
}
}