Exporting TableView Data in Xcode/Swift - ios

I am having users input a list of "serials" into a TableView called "Equipment". Here is the code for the tasks array.
var taskMgr: TaskManager = TaskManager()
struct task {
var serial = "Un-Named"
//var desc = "Un-Named"
}
class TaskManager: NSObject {
//Sets up array of Tasks
var tasks = [task]()
//Add Task Function
func addTask(serial: String){
tasks.append(task(serial: serial))
}
}
I want to take the table data and export it to a .txt file or .csv with each task on separate lines. What is the best way to do this? Please help I've been stuck for a few days. I'm not sure which way I should approach this.
Thanks!

String.writeToFile should be able to do this for you if you map your task array into a string array and then join it into one string with new line as separators
let tasksString = tasks.map({ (task) -> String in
return task.serial
}).joinWithSeparator("\n")
do {
try tasksString.writeToFile(pathToFile, atomically: true, encoding: NSUTF8StringEncoding)
} catch {
}

Related

What is the proper way to read values from Firebase considering the MVC approach?

I'm quite new to Swift and currently dealing with the Firebase-Database.
I managed to realise the functions that I want to have, but my implementation feels not right.
Most I am struggling with the closures, that I need to get data from Firebase.
I tried to follow the MVC approach and have DataBaseManager, which is getting filling my model:
func getCollectionData(user: String, callback: #escaping([CollectionData]) -> Void) {
var dataArray: [CollectionData] = []
var imageArray:[String] = []
let db = Firestore.firestore()
db.collection(user).getDocuments() { (QuerySnapshot, err) in
if let err = err {
print("Error getting documents : \(err)")
}
else {
for document in QuerySnapshot!.documents {
let album = document.get("album") as! String
let artist = document.get("artist") as! String
let genre = document.get("genre") as! String
let location = document.get("location") as! String
var a = CollectionData(album: album, artist: artist, imageArray: imageArray, genre: genre, location: location)
a.imageArray!.append(document.get("fronturl") as? String ?? "No Image")
a.imageArray!.append(document.get("backurl") as? String ?? "No Image")
a.imageArray!.append(document.get("coverlurl") as? String ?? "No Image")
dataArray.append(a)
}
callback(dataArray)
}
}
}
With this I'm getting the information and the downloadlinks, which I later use in a gallery.
Is this the right way?
I feel not, because the fiddling starts, when I fetch the data from my ViewController:
var dataArray = []
dataBaseManager.getCollectionData(user: user) { data in
self.dataArray = data
I can see, that I sometimes run into problems with timing, when I use data from dataArray immediately after running the closure.
My question is, this a valid way to handle the data from Firebase or is there a more elegant way to achieve this?
You are on the right track. However, using dataArray immediately is where the issue could be.
Let me provide a high level example with some pseudo code as a template:
Suppose you have an ToDo app; the user logs in and the first view they see is all of their current To Do's
class viewController
var dataArray = [ToDo]() //a class property used as a tableViewDataSource
#IBOutlet toDoTableView
viewDidLoad {
loadToDos()
}
func loadToDos() {
thisUsersToDoCollection.getDocuments() { documents in
self.array.append( the to Do Documents)
self.toDoTableView.reloadData()
}
}
}
With the above template you can see that within the Firebase .getDocuments function, we get the To Do documents from the colleciton, populate the dataSource array and THEN reload the tableView to display that data.
Following this design pattern will alleviate this issue
I sometimes run into problems with timing, when I use data from
dataArray immediately after running the closure
Because the user cannot interact with the data until it's fully loaded and populated within the tableView.
You could of course do a callback within the loadToDos function if you prefer so it would then be called like this - only reload the tableView once the loadToDos function has completed
viewDidLoad {
loadToDos {
toDoTableView.reloadData()
}
}
The big picture concept here is that Firebase data is ONLY VALID WITHIN THE CLOSURE following the Firebase call. Let that sequence provide pacing to your app; only display info if it's valid, only allow the user to interact with the data when it's actually available.

How to make firebase .observeSingleEventOf function run synchronously

I am running Firebase's .getSingleEventOf function to read data from my database in my program, and it is an asynchronous function. How would I make it synchronous (or make another synchronous function to house the code)?
I've tried to use the data passed through the function, but it hasn't been working! It only returns 0 elements, even though I know that I have data with the specific letters in my database.
import Foundation
import Firebase
struct CompeteUserFinderService {
static func findCompetitors(contains letters: String?) -> [String] {
//Make sure there was an input
guard let letters = letters else { return [] }
var usernames = [String]()
//Database reference
let ref = Database.database().reference().child("usernames")
ref.observeSingleEvent(of: .value) { (snapshot) in
//Creates an array of all values in the usernames branch
let value = Array((snapshot.value as! [String: Any]).keys)
usernames = value.map { $0.lowercased() }.filter { $0.contains(letters.lowercased()) }
}
return usernames
}
}

Extracting data from API (JSON format) doesn't save data outside of function call

I am trying to get an array of temperatures in a given time period from an API in JSON format. I was able to retrieve the array through a completion handler but I can't save it to another variable outside the function call (one that uses completion handler). Here is my code. Please see the commented area.
class WeatherGetter {
func getWeather(_ zip: String, startdate: String, enddate: String, completion: #escaping (([[Double]]) -> Void)) {
// This is a pretty simple networking task, so the shared session will do.
let session = URLSession.shared
let string = "api address"
let url = URL(string: string)
var weatherRequestURL = URLRequest(url:url! as URL)
weatherRequestURL.httpMethod = "GET"
// The data task retrieves the data.
let dataTask = session.dataTask(with: weatherRequestURL) {
(data, response, error) -> Void in
if let error = error {
// Case 1: Error
// We got some kind of error while trying to get data from the server.
print("Error:\n\(error)")
}
else {
// Case 2: Success
// We got a response from the server!
do {
var temps = [Double]()
var winds = [Double]()
let weather = try JSON(data: data!)
let conditions1 = weather["data"]
let conditions2 = conditions1["weather"]
let count = conditions2.count
for i in 0...count-1 {
let conditions3 = conditions2[i]
let conditions4 = conditions3["hourly"]
let count2 = conditions4.count
for j in 0...count2-1 {
let conditions5 = conditions4[j]
let tempF = conditions5["tempF"].doubleValue
let windspeed = conditions5["windspeedKmph"].doubleValue
temps.append(tempF)
winds.append(windspeed)
}
}
completion([temps, winds])
}
catch let jsonError as NSError {
// An error occurred while trying to convert the data into a Swift dictionary.
print("JSON error description: \(jsonError.description)")
}
}
}
// The data task is set up...launch it!
dataTask.resume()
}
}
I am calling this method from my view controller class. Here is the code.
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
}
//Here it prints out an empty array
print(weatherData)
}
The issue is that API takes some time to return the data, when the data is return the "Completion Listener" is called and it goes inside the "getWeather" method implementation, where it prints the data of array. But when your outside print method is called the API hasn't returned the data yet. So it shows empty array. If you will try to print the data form "weatherData" object after sometime it will work.
The best way I can suggest you is to update your UI with the data inside the "getWeather" method implementation like this:
override func viewDidLoad() {
super.viewDidLoad()
let weather = WeatherGetter()
weather.getWeather("13323", startdate: "2016-10-01", enddate: "2017-04-30") { (weatherhandler: [[Double]]) in
//It prints out the correct array here
print(weatherhandler[0])
weatherData = weatherhandler[0]
// Update your UI here.
}
//Here it prints out an empty array
print(weatherData)
}
It isn't an error, when your controller get loaded the array is still empty because your getWeather is still doing its thing (meaning accessing the api, decode the json) when it finishes the callback will have data to return to your controller.
For example if you were using a tableView, you will have reloadData() to refresh the UI, after you assign data to weatherData
Or you could place a property Observer as you declaring your weatherData property.
var weatherData:[Double]? = nil {
didSet {
guard let data = weatherData else { return }
// now you could do soemthing with the data, to populate your UI
}
}
now after the data is assigned to wheaterData, didSet will be called.
Hope that helps, and also place your jsonParsing logic into a `struct :)

Using String in another swift class

I am trying to use the array of strings that is named "theList" in another class in my project. I understand that the variable is declared within a method and therefore not of global scope. But how can I fix that? I have tried a few things and nothing is working. What is the best way to accomplish this?
EDIT: All I want to do is set "theList" equal to "Body"
[![enter image description here][1]][1]
I hope this clears it up a little bit, and I can select an answer. Thanks to everyone!
I think if you want to access that list outside of the function you should simply make it a variable of the class.
class TaskManager: NSObject {
//Sets up array of Tasks
var tasks = [task]()
var theList = [String]()
//Add Task Function
func addTask(serial: String){
tasks.append(task(serial: serial))
theList = tasks.map({ (task) -> String in
return task.serial
})
}
}
Your best way to do this is to look at the scope of your class and the declaration. Define your list in a place that has scope everywhere you want to use it, or make it global. That's how I do this kind of task. Sometimes add it to a higher level class declaration, and sometimes I make it global to, say, the entire program. Depends on the task and my outlook at the time of coding.
Let you function return the results of adding the task then:
class TaskManager: NSObject {
// declare your variables
func addTask(serial: String) -> [String] {
// put your realisation
}
}
or make your variable with serials available publicly:
class TaskManager: NSObject {
var tasks = [Task]()
public var serials = [String]()
func addTask(serial: String) {
// put your realisation
// save the results to serials
}
}
and then access it via the instance.
Adding theList Variable above class made it global.
Then I had to remove let from the addTask function.
//Add Task Function
func addTask(serial: String){
tasks.append(task(serial: serial))
let theList = tasks.map({ (task) -> String in
return task.serial
}).joinWithSeparator("\n")
Became
//Add Task Function
func addTask(serial: String){
tasks.append(task(serial: serial))
theList = tasks.map({ (task) -> String in
return task.serial
}).joinWithSeparator("\n")
The final code is as follows.
import UIKit
var theList : String = String()
var taskMgr: TaskManager = TaskManager()
struct task {
var serial = "Un-Named"
}
public class TaskManager: NSObject {
//Sets up array of Tasks
var tasks = [task]()
//Add Task Function
func addTask(serial: String){
tasks.append(task(serial: serial))
theList = tasks.map({ (task) -> String in
return task.serial
}).joinWithSeparator("\n")
do {
//try tasksString.writeToFile(pathToFile, atomically: true, encoding: NSUTF8StringEncoding)
print(theList)
}
}
}
I selected the answer. Thank you to all who helped cure my tired eyes.

Populate array of custom classes from two different network requests

In my Swift iOS project, I am trying to populate an array of custom class objects using JSON data retrieved with Alamofire and parsed with SwiftyJSON. My problem, though, is combining the results of two different network request and then populating a UITableView with the resulting array.
My custom class is implemented:
class teamItem: Printable {
var name: String?
var number: String?
init(sqljson: JSON, nallenjson: JSON, numinjson: Int) {
if let n = sqljson[numinjson, "team_num"].string! as String! {
self.number = n
}
if let name = nallenjson["result",0,"team_name"].string! as String! {
self.name = name
}
}
var description: String {
return "Number: \(number) Name: \(name)"
}
}
Here is my viewDidLoad():
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
refresh() {
() -> Void in
self.tableView(self.tableView, numberOfRowsInSection: self.teamsArr.count)
self.tableView.reloadData()
for item in self.teamsArr {
println(item)
}
return
}
self.tableView.reloadData()
}
which goes to the refresh() method:
func refresh(completionHandler: (() -> Void)) {
populateArray(completionHandler)
}
and finally, populateArray():
func populateArray(completionHandler: (() -> Void)) {
SqlHelper.getData("http://cnidarian1.net16.net/select_team.php", params: ["team_num":"ALL"]) {
(result: NSData) in
let jsonObject : AnyObject! = NSJSONSerialization.JSONObjectWithData(result, options: NSJSONReadingOptions.MutableContainers, error: nil)
let json = JSON(jsonObject)
self.json1 = json
println(json.count)
for var i = 0; i < json.count; ++i {
var teamnum = json[i,"team_num"].string!
NSLog(teamnum)
Alamofire.request(.GET, "http://api.vex.us.nallen.me/get_teams", parameters: ["team": teamnum])
.responseJSON { (req, res, json, err) in
let json = JSON(json!)
self.json2 = json
self.teamsArr.append(teamItem(sqljson: self.json1, nallenjson: self.json2, numinjson: i))
}
}
completionHandler()
}
}
the first problem I had was that i in the for loop reached 3 and caused errors when I thought it really shouldn't because that JSON array only contains 3 entries. My other main problem was that the table view would be empty until I manually triggered reloadData() with a reload button in my UI, and even then there were problems with the data in the tables.
really appreciate any assistance, as I am very new to iOS and Swift and dealing with Alamofire's asynchronous calls really confused me. The code I have been writing has grown so large and generated so many little errors, I thought there would probably be a better way of achieving my goal. Sorry for the long-winded question, and thanks in advance for any responses!
The Alamofire request returns immediately and in parallel executes the closure, which will take some time to complete. Your completion handler is called right after the Alamofire returns, but the data aren't yet available. You need to call it from within the Alamofire closure - this ensures that it is called after the data became available.

Resources