After parsing JSON I got my data in arraOfData of ViewController, but collectionView not display this. If I use static array all working.
collectionview.reloadData() is not working. In my opinion, need somehow waiting data from parsing, then try load collectionView. I tried use refresherControler for waiting data, but I don't know where put method "endRefreshing" (i guess startRefreshing need in ViewDidLoad)
If I use reloadData like didSet in arraOfData
I got error cause collectionView is nil. Use reloadData right after got data from parsing same didn't work for me
And a big request to tell a couple of resources (articles) on this topic, I feel that I have big problems with displaying data and in what sequence functions start to load. Thanks a lot.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
var arraOfData = [GettingMoney]() {
didSet {
print("Changes: \(arraOfData.count)")
}
}
// let arr = ["1", "2", "3"] - test, if i use this array, collectionView display data
override func viewDidLoad() {
super.viewDidLoad()
GettingMoney.fetchData()
collectionView.dataSource = self
collectionView.delegate = self
}
}
extension ViewController: UICollectionViewDelegate,
UICollectionViewDataSource {
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return arraOfData.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "collectionCellId", for: indexPath) as? CollectionViewCell
cell?.currentCurrency.text = "\(arraOfData[indexPath.row])"
return cell!
}
}
As this
GettingMoney.fetchData()
is a sign of asynchronous process that doesn't reload the collection , so do
func fetchData(completion:#escaping ([String]) -> ()) {
Api.load {
completion(arr)
}
}
Then use like
GettingMoney.fetchData { arr in
self.arraOfData = arr
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
A not direct case like yours is here Returning data from async call in Swift function
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 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'm doing a practice, and find my class property(adNames) can't be assigned. But it can be assigned outside(the place where I comment out). Here is my code:
import UIKit
import Alamofire
import SwiftyJSON
class AdTableViewController: UITableViewController {
var adNames: [JSON]?
override func viewDidLoad() {
super.viewDidLoad()
Alamofire.request(.GET, "http://codewithchris.com/code/afsample.json").responseJSON { (Response) -> Void in
if let value = Response.result.value{
let json = JSON(value)
print(json["secondkey"].arrayValue) //["item1","item2","item3"]
self.adNames = json["secondkey"].arrayValue
}
}
// self.adNames = ["item1", "item2", "item3"]
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let names = adNames {
return names.count
}
return 0
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
}
}
Can anybody tell me the reason? Thanks!
Your Alamofire request runs in the background. Meanwhile, your tableView wants to display and calls numberOfRowsInSection. By this time, Alamofire has not finished its work, so adnames hasn't been set.
You need to refresh the tableView with reloadData() after adNames is set. i.e.
Alamofire.request(.GET, "http://codewithchris.com/code/afsample.json").responseJSON { (Response) -> Void in
if let value = Response.result.value{
let json = JSON(value)
self.adNames = json["secondkey"].arrayValue
self.tableView.reloadData()
}
}
Then the tableView will refresh and re-call numberOfRowsInSection, and at this time, adNames will not be nil.
You are accessing self inside a closure which is passed to the request call. Use capture lists inside the closure to hold the references to the self object.
[weak self] (Response) -> Void in (in this case you have to use self? to access the self instance)
...
or [unowned self] (Response) -> Void in
If you are sure that the object will be present after the request call completes.
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
}
}