How can I expand inner scope value in Swift - ios

I started develop not long. So I have problem in my code. I want return data.count in TableView function. But I can't get value of data: [DataGroup] in db.collection(). It isn't have value out of db.collection() scope. So how can I get data from that scope?
class RecordGroupViewController: UIViewController, UITableViewDataSource {
let db = Firestore.firestore()
var data: [DataGroup] = []
override func viewDidLoad() {
super.viewDidLoad()
fetchDataGroup()
}
func fetchDataGroup() {
db.collection("data").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
do {
let docuData = try JSONSerialization.data(withJSONObject: document.data(), options: [])
let decoder = JSONDecoder()
let groupData: DataGroup = try decoder.decode(DataGroup.self, from: data)
self.data.append(groupData)
} catch let error {
print("---> error: \(error.localizedDescription)")
}
}
}
}
print("\(self.recordGroups.count)")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return UITableViewCell()
}
}

From what I understood of your question, you want to access your data variable's count.
You can do so from anywhere in your code using self.data.count
However, if you want your tableView to use this new value :
Link your tableview to your controller using an IBOutlet (or create a reference to it if you instantiate it programmatically)
After you have appended your data with the new data, call self.yourTableViewReference.reloadData(), this will make the tableView call your dataSource method numberOfRowsInSection with the newly appended data.

Related

How to pass API data to table view cells

I'm having some trouble passing my API returned data to table view cells. I am appending the data to an array and then passing this array to the table view (as usual) to get the number of rows and data for the cells. When I print inside the function where I am appending, the titles are shown in the array. Outside they're not. Any idea? Relevant code below:
class ProductTableViewController: UITableViewController, UISearchBarDelegate {
#IBOutlet weak var searchBar: UISearchBar!
#IBOutlet var tabView: UITableView!
var filteredData = ["Title1"]
override func viewDidLoad() {
super.viewDidLoad()
getProducts { (products) in
for product in products {
self.filteredData.append(product.title)
}
}
}
func getProducts(completionHandler: #escaping([ProductDetail]) -> Void) {
let url = URL(string: "exampleAPIURL")!
let dataTask = URLSession.shared.dataTask(with: url) {data, _, _ in
guard let jsonData = data else { return }
do {
let decoder = JSONDecoder()
let productsResponse = try decoder.decode(Products.self, from: jsonData)
let productDetails = productsResponse.data
for name in productDetails {
self.filteredData.append(name.title)
}
completionHandler(productDetails)
}catch {
print(error.localizedDescription)
}
}
dataTask.resume()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if filteredData == nil {
return 1 }
else {
return filteredData.count
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as UITableViewCell
for name in filteredData {
if name != nil {
let product = filteredData[indexPath.row]
cell.textLabel?.text = product
} else {
cell.textLabel?.text = "name"
}
}
return cell
}
I am only receiving the hardcoded strings in the filteredData array when I run the simulator. Is there a different way to pass the JSON?
Many thanks!
Reload the table view after the data is collected:
getProducts { (products) in
for product in products {
self.filteredData.append(product.title)
}
self.tabView.reloadData()
}
After setting the array, you need to call self.tableView.reloadData() and invoke it on the main thread.
Also, its better to do the products API call from viewDidAppear as if the API call from viewDidLoad returns fast enough, operations on the view might fail. Also you might want to show some activity indicator.
override func viewDidAppear() {
super.viewDidLoad()
getProducts { (products) in
for product in products {
self.filteredData.append(product.title)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}

can't set data to the variable when i parse json

I am currently trying to set data I get from parsing my json to a variable in my ViewController from RawDataCategory file, which decodes json.
Here is how I call a static method in CategoryViewController
class CategoryViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var categoryProducts: [Datum]? // this is the variable i want with data i get
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
Datum.fetchProducts { (categoryProductsFromJSON) -> () in // here i call a static function.. i will paste the code below
self.categoryProducts = categoryProductsFromJSON //here i set value
print(self.categoryProducts?[0].name) // if i try to print the value in the scope i can access it. and it shows with no problem
}
print(categoryProducts?[0].name) //here is the problem. when i get out of the scope it return nil.
}
}
I can access the variable in the scope but somehow when i try to call it outside it prints nil.
Here is the static function (method) i call to get decoded json:
static func fetchProducts(_ completionHandler: #escaping ([Datum]) -> ()) {
guard let url = URL(string: "http://localhost:8888/dayhandan/public/api/v1/category/1") else { return }
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url, completionHandler: { (data, response, error) -> Void in
if error != nil {
print("error")
return
}
do {
print("no error so far")
guard let data = data else { return }
let rawData = try JSONDecoder().decode(CategoryRawData.self, from: data)
DispatchQueue.main.async(execute: { () -> Void in
completionHandler(rawData.data ?? [])
})
} catch let err {
print(err)
}
})
task.resume()
}
I don't get any errors while i decode json. In fact I can pass it to my ViewContoller. There are no errors overall. categoryProducts just becomes nil when i call it outside the scope.
Is this a good way to use decoded data. if it is can someone help me to solve the issue. or can someone point at a good way to use decoded data. Thanks for your time
You should only try to use the result of fetchProducts in the completion handler because the completion handler will be executed at some point in the future. Anything outside of the completion handler will be executed immediately.
Reload your tableView from within the completion handler:
Datum.fetchProducts { (categoryProductsFromJSON) -> () in
self.categoryProducts = categoryProductsFromJSON
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Then you should have some logic to populate the tableView:
extension CategoryViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return categoryProducts?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// something like this
guard let category = categoryProducts?[indexPath.row] else { return UITableViewCell() }
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
cell.titleLabel?.text = category.title
return cell
}
}

Cant display data after using .getDocuments in Firestore and trying to place the document IDs into an array and displaying them using a tableview

I have a class
class List {
var name: String
init(name: String) {
self.name = name
}
}
which is used to make the array. I declared the array
var providersList = [List]()
then used the function
func downloadData() {
db.collection("\(finalSelection.lowercased())_providers").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
self.providersList.append(List(name: "\(document.documentID)"))
}
}
}
}
to download the data. The data downloads correctly because there is no error and I also used print() to confirm the data is correct. However, when I use the functions for tableview
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MoreCell", for: indexPath) as! MoreInfoTableViewCell
cell.moreInfoLabel.text = providersList[indexPath.row].name
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return providersList.count
}
the table view isn't populated. I know this method works because I have used something similar to it in another area of my code (not downloading the data but populating the table view)
Edit: I tried to remove the class and append to the array normally without using the .name from the class. However, this didn't do anything either
Ok, so I found out the solution to this when I was laying in bed about to sleep (lol). In the function downloadData(), I have to put self.tableView.reloadData() after appending to the array. I will include this for anyone who faced a similar problem like me.
func downloadData() {
db.collection("\(finalSelection.lowercased())_providers").getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
self.providersList.append(List(name: "\(document.documentID)"))
self.tableView.reloadData()
}
}
}
}

How to implement UISearchBar to filter name or capital JSON using JSON Decoder in swift iOS application

How to implement UISearchBar to filter name or capital JSON using JSON Decoder in swift iOS application. I want to implement UISearchBar and search results or filter results using name from JSON Data.
import UIKit
Structure Created
struct jsonstruct:Decodable
{
let name:String
let capital:String
}
class ViewController: UIViewController,UITableViewDataSource,UITableViewDelegate, UISearchBarDelegate, UISearchControllerDelegate, UISearchDisplayDelegate {
Creating Outlet for TableView and SearchBar
#IBOutlet var tableview: UITableView!
#IBOutlet var searchBar: UISearchBar!
Declaring JSON
var arrdata = [jsonstruct]()
Function for getting Data
func getdata()
{
let url = URL(string: "https://restcountries.eu/rest/v2/all")
URLSession.shared.dataTask(with: url!)
{
(data, response, error) in
do
{
if error == nil
{
self.arrdata = try
JSONDecoder().decode([jsonstruct].self, from: data!)
for mainarr in self.arrdata
{
print(mainarr.name,":",mainarr.capital as Any)
DispatchQueue.main.async
{
self.tableview.reloadData()
}
}
}
}
catch
{
print(error.localizedDescription)
}
}.resume()
}
TABLE VIEW
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.arrdata.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.label1.text = "Name: \(arrdata[indexPath.row].name)"
cell.label2.text = "Capital: \(arrdata[indexPath.row].capital)"
return cell
}
OverRiding Function
override func viewDidLoad()
{
getdata()
}
You need to make two objects of data, one original data and other filtered data.
var filteredArrData = [jsonstruct]()
var arrdata = [jsonstruct]()
Than in your getData functions:
do {
self.arrdata = try JSONDecoder().decode([jsonstruct].self, from: data!)
self.filteredArrData = self.arrdata
}
Then in your table view delegate and data source:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return self.filteredArrData.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:TableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell") as! TableViewCell
cell.label1.text = "Name: \(filteredArrData[indexPath.row].name)"
cell.label2.text = "Capital: \(filteredArrData[indexPath.row].capital)"
return cell
}
Than make filter function like this:
func applyFilters(textSearched: String) {
filteredArrData = arrdata.filter({ item -> Bool in
return item.name.lowercased().hasPrefix(textSearched.lowercased())
})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Then pass your string to this function and everything will work fine.
Make TextField with an IBAction of didbegin like below and create an array where you can have filtered data.
#IBAction func tfSearch(_ sender: UITextField) {
let filteredArray = yourArr.filter { $0.contains(sender.text) }
}
Assuming you are not caching all your data and the filtering is done live via an API. You will need to set an object or the viewcontroller as a delegate of the search bar(UISearchBarDelegate). Then use the searchText as the text for your API query.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
//call throttle that will call urlsession
}
Since one character is typed at a time we do not to call the API every time. You may need to use a throttler to make lesser API calls instead of sending character by character search. You might find this tutorial about throttling helpful: Simple Throttling in Swift .
Most REST APIs should have a filter feature and you could just easily append the typed name or capital.
https://restcountries.eu/rest/v2/name/append name here
https://restcountries.eu/rest/v2/capital/append capital here
This is an example networking code to fetch the results. Use the results to call another method safely on the main queue to reload the tableview.
if let url = URL(string: "https://restcountries.eu/rest/v2/country?q=name") {
URLSession.shared.dataTask(with: url) { data, response, error in
if let data = data {
do {
let results = try JSONDecoder().decode(YourCustomDecodeStruct.self, from: data)
//safely your data source and reload the tableview
} catch let error {
print(error)
}
}
}.resume()
}

Use data on JSON with cells

I've got this JSON from an API:
{
"oldest": "2019-01-24T00:00:00+00:00",
"activities": [
{
"message": "<strong>Henrik</strong> didn't resist a guilty pleasure at <strong>Starbucks</strong>.",
"amount": 2.5,
"userId": 2,
"timestamp": "2019-05-23T00:00:00+00:00"
},
{
"message": "<strong>You</strong> made a manual transfer.",
"amount": 10,
"userId": 1,
"timestamp": "2019-01-24T00:00:00+00:00"
}
]
}
It has a lote more activities. How can I access it and fill my cells with its data? So far I've got this code:
MainViewController:
struct Activities: Decodable {
var oldest: String
var activities: [Activity]
}
struct Activity: Decodable {
var message: String
var amount: Float
var userId: Int
var timestamp: String
}
class MainTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
var activityList: [Activities] = []
var activity: [Activity] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.prefetchDataSource = self
let activitiesJSONURLString = "https://qapital-ios-testtask.herokuapp.com/activities?from=2016-05-23T00:00:00+00:00&to=2019-05-23T00:00:00+00:00"
guard let activitiesURL = URL(string: activitiesJSONURLString) else { return }
URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
// perhaps check err
// also perhaps check response status 200 OK
guard let data = data else { return }
do {
// Activities
let activities = try JSONDecoder().decode(Activities.self, from: data)
} catch let jsonErr {
print("Error serializing json: ", jsonErr)
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}.resume()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activityList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
return cell
}
// Prefetching
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
// if indexPaths.contains(where: isLoadingCell) {
// viewModel.fetchModerators()
// }
}
}
But I think something is off. Or I have no clue on how to start. I could really use some help or any tips you can give me. Please and thank you!
First of all the naming of the structs is pretty confusing. Name the root object with something unrelated like Response or Root.
And we are going to decode the timestamps as Date
struct Root: Decodable {
var oldest: Date
var activities: [Activity]
}
struct Activity: Decodable {
var message: String
var amount: Float
var userId: Int
var timestamp: Date
}
Second of all as the data is received in all the conformance to UITableViewDataSourcePrefetching is pointless. Remove it and delete also the prefetchRowsAt method.
Declare only one data source array and name it activities
var activities = [Activity]()
and delete
var activityList: Activities!
In the completion handler of the data task decode Root and assign the activities array to the data source array
do {
// Activities
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let result = try decoder.decode(Root.self, from: data)
self.activities = result.activities
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch {
print("Error serializing json: ", error)
}
The table view data source methods are
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
let activity = activities[indexPath.row]
// assign the activity data to the UI for example
// cell.someLabel = activity.amount
return cell
}
Because you are using the activityList to determine the number of rows, I'm assuming that you want to use the data from activityList in order to populate your ActivityCells. That is, unless, you meant for activityList to be a single instance of Activities instead of an array of Activities, in which case you would likely use activityList.activities.count in order to determine the number of rows. In either case, lets just call the array of data you want to use to fill the cells activityList.
In this case, you should make sure to update activityList to the activities that you have fetched from the API. Once you have the activityList, you can then use reloadData which will trigger your table view delegate methods. In tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) you can then use activityList in order to update the dequeued cell.
Something like this might be what you want:
class MainTableViewController: UITableViewController, UITableViewDataSourcePrefetching {
var activityList: Activities!
var activity: [Activity] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.prefetchDataSource = self
let activitiesJSONURLString = "https://qapital-ios-testtask.herokuapp.com/activities?from=2016-05-23T00:00:00+00:00&to=2019-05-23T00:00:00+00:00"
guard let activitiesURL = URL(string: activitiesJSONURLString) else { return }
URLSession.shared.dataTask(with: activitiesURL) { (data, response, err) in
// perhaps check err
// also perhaps check response status 200 OK
guard let data = data else { return }
do {
// Activities
let activities = try JSONDecoder().decode(Activities.self, from: data)
self.activityList = activities
DispatchQueue.main.async {
self.tableView.reloadData()
}
} catch let jsonErr {
print("Error serializing json: ", jsonErr)
}
}.resume()
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return activityList.activities.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ActivityCell", for: indexPath) as! MainTableViewCell
if let activity = activityList?[indexPath.row] {
// UPDATE CELL ACCORDING TO activity
}
return cell
}
}

Resources