Safe handling of Apple TabularData table data in Swift - ios

I have successfully imported a simple two-column CSV file using DataFrame.
Now I want to turn the two cells in each row into strings.
The left or right side will occasionally be missing a value; see print output.
table
When I try to process a row with a nil cell, the program crashes with "Fatal error: Unexpectedly found nil while unwrapping an Optional value"
So my question is, how do I turn the cells into strings ("" if Nil) safely?
Complete ContentView code below, thanks in advance for any help.
import SwiftUI
import TabularData
struct ContentView: View {
#State var openFile = false
var body: some View {
VStack {
Button(action: {openFile.toggle()}, label: {
Text("Open")
})
}.fileImporter(
isPresented: $openFile,
allowedContentTypes: [.commaSeparatedText],
allowsMultipleSelection: false) { (result) in
do {
let fileURL = try result.get().first
if fileURL!.startAccessingSecurityScopedResource() {
print(fileURL!)
importTable(url: fileURL!)
}
fileURL!.stopAccessingSecurityScopedResource()
} catch {
print("Unable to read file contents")
print(error.localizedDescription)
}
}
}
func importTable(url: URL) {
var importerTable: DataFrame = [:]
let options = CSVReadingOptions(hasHeaderRow: false, delimiter: ",")
do {
importerTable = try DataFrame(
contentsOfCSVFile: url,
options: options)
} catch {
print("ERROR reading CSV file")
print(error.localizedDescription)
}
print("\(importerTable)")
importerTable.rows.forEach { row in
let leftString = row[0]! as! String
let rightString = row[1]! as! String
print("Left: \(leftString) Right: \(rightString)")
}
}
}

A fileImporter context is quite insecure, any carelessly written exclamation mark can crash the app.
First you have to check fileURL and abort the import if it's nil
guard let fileURL = try result.get().first else { return }
if fileURL.startAccessingSecurityScopedResource() {
print(fileURL)
importTable(url: fileURL)
}
fileURL.stopAccessingSecurityScopedResource()
In importTable you must not continue after a do - catch if an error is thrown and you have to check if the CSV file contains three items per row.
func importTable(url: URL) {
var importerTable: DataFrame = [:]
let options = CSVReadingOptions(hasHeaderRow: false, delimiter: ",")
do {
importerTable = try DataFrame(
contentsOfCSVFile: url,
options: options)
print("\(importerTable)")
importerTable.rows.forEach { row in
if row.count > 2 {
let leftString = row[1] as! String
let rightString = row[2] as! String
print("Left: \(leftString) Right: \(rightString)")
}
}
} catch {
print("ERROR reading CSV file")
print(error.localizedDescription)
}
}

Related

Async/Await: "out of range error" when trying to access array filled with downloaded images from firebase storage

I tried to download successfully uploaded images to storage with the code:
func retrieveAllPictures(helloid: String) async {
//Referenzen zu den Datenbanken
let db = Firestore.firestore()
//Alle Foto Ids in einem Array speichern
let result = try? await db.collection("events").document(helloid).getDocument()
allPictureIDs = (result?.data()!["pictures"] as? [String])!
var image = UIImage()
for path in allPictureIDs {
let storage = Storage.storage().reference()
let fileRef = storage.child(path)
try? await fileRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
return
} else {
image = UIImage(data: data!)!
self.retrievedEventImages.append(image)
}
}
}
}
this is how I try to access the array at index 0:
struct finalEventView: View {
#EnvironmentObject var viewModel: AppViewModel
var id: String
var body: some View {
NavigationView {
VStack{
if (viewModel.retrievedEventImages[0] != nil){
Image(uiImage: viewModel.retrievedEventImages[0]!)
}
/*
ForEach(viewModel.EventImages, id: \.self){ image in
Image(uiImage: image)
.resizable()
.frame(width: 110, height: 110)
}*/
Button(action: {
Task {
try? await viewModel.retrieveAllPictures(helloid: self.id)
}
}, label: {Text("print image arr")})
}}
.onAppear{
Task {
try? await viewModel.retrieveAllPictures(helloid: self.id)
}
}
}
}
when trying to debug the code, I see that retrievedEventImages is filled with the UIImages
still when trying to access the array at index 0 I get an out of range error
maybe someone knows how to fix it, any help is appreciated
Never access an item of an array in a SwiftUI view rendering area by index.
In most cases the view is rendered the first time while the array is empty which causes the out of range crash.
This kind of checking for the existence of an item is unswifty anyway, just use first and Optional Binding
if let firstImage = viewModel.retrievedEventImages.first {
Image(uiImage: firstImage)
}
Edit: Apparently there is no async version of getData(). You can adopt async/await with a Continuation
do {
let data : Data = try await withCheckedThrowingContinuation { continuation in
fileRef.getData(maxSize: 1 * 1024 * 1024) { data, error in
if let error = error {
continuation.resume(throwing: error)
} else {
continuation.resume(returning: data!)
}
}
}
let image = UIImage(data: data)!
self.retrievedEventImages.append(image)
} catch {
print(error)
// or better show a message to the user
}
See #vadian answer for an explanation of part of the issue.
To add an await/async code solution as well. I'm doing this on macOS - the iOS solution is similar.
Task {
let resultData = try! await imageRef.data(maxSize: 1 * 1024 * 1024)
let image = NSImage(data: resultData) //UIImage for iOS
self.retrievedEventImages.append(image)
}
Firebase used the await/async func data instead of getData to avoid naming collisions.
There's probably more up-to-date info but see git 8289 for further reading.

Is there a workaround for Converting from <operationQuery.Data> to Data type?

Language:Swift
Hello, I'd like some help in resolving an error being thrown when I try to retrieve data from an Apollo GraphQL request that I'm making. The API in use is the AniList API utilizing GraphQL.
Here's what I've tried:
In my model I'm making the Apollo GraphQL query inside of a search() function. I want to then use the Codable protocol to fill an array of anime objects. Currently it's setup to return just for 1 anime object. I was planning on using this anime list as a data set for TableView later. I wanted to take small steps so my current goal is to at least get the Codable protocol to work and return the response data to an anime Struct object.
The documentation for Apollo shows how to get individual fields but when I try to get the corresponding fields from my response , I don't even have the option.
func search(){
Network.shared.apollo.fetch(query: AnisearchQuery()){ result in
guard let data = try? result.get().data else { return }
var topData:APIResponse?
do{
topData = JSONDecoder().decode(APIResponse.self, from: data.self)
}catch{
}
}
}
Here are the data structures that I've set up as a representation of the JSON data I expect to receive with respect to the hierarchy it is laid out in the response.
struct APIResponse:Codable{
let data:data
}
struct data:Codable{
let Page:page
let media:media
}
struct media:Codable{
let animeResults:anime
}
struct anime:Codable{
var romaji:String
var english: String
var native:String
var episodes:Int
var duration:Int
var medium:String
}
Here is the error in question.
"Cannot convert value of type 'AnisearchQuery.Data' to expected argument type 'Data'". This is generated by this line of code
topData = JSONDecoder().decode(APIResponse.self, from: data.self)
For further context , AnisearchQuery.Data is generated in response to the query I created for the codgen.
Here's what the data would look like in JSON format
This is the setup of the query:
query anisearch($page:Int, $perPage:Int, $search:String){
Page (page:$page, perPage:$perPage){
pageInfo {
total
currentPage
lastPage
hasNextPage
perPage
}
media(search:$search){
title{
romaji
english
native
}
episodes
duration
coverImage{
medium
}
}
}
}
Here's the Data object in the API.swift file:
public struct Data: GraphQLSelectionSet {
public static let possibleTypes: [String] = ["Query"]
public static var selections: [GraphQLSelection] {
return [
GraphQLField("Page", arguments: ["page": GraphQLVariable("page"), "perPage": GraphQLVariable("perPage")], type: .object(Page.selections)),
]
}
I'd be open to any alternative methods as to getting this task done or perhaps fixes to the error being thrown.
Many thanks in advance.
Inefficient Workaround
var animeCollection:SearchAnimeQuery.Data?
var media:[SearchAnimeQuery.Data.Page.Medium]?
var filteredData:[SearchAnimeQuery.Data.Page.Medium] = []
func loadData(search:String = "") {
if !search.isEmpty{
Network.shared.apollo.fetch(query: SearchAnimeQuery(search: search)){
[weak self] result in
//Make Sure ViewController Has not been deallocated
guard let self = self else{
return
}
/*defer {
}*/
switch result {
case .success(let graphQLResult):
if let animeData = graphQLResult.data {
DispatchQueue.main.async {
self.animeCollection = animeData
self.media = self.animeCollection?.page?.media as! [SearchAnimeQuery.Data.Page.Medium]
self.filteredData = self.media!
self.tableView.reloadData()
}
}
if let errors = graphQLResult.errors {
let message = errors
.map { $0.localizedDescription }
.joined(separator: "\n")
print(message)
}
case .failure(let error):
print(error.localizedDescription)
}
}
}
}

getting names displaying from firebase

I am trying to display the contents of a firebase database. I know that I am reading them correctly as I am able to print them as they are read in. The problem is when I call the method to display them on screen, they are "out of range".
I know this means the the methods are being called simultaneously therefore the array is empty. I have tried the "Sleep()" method and doesn't work.
//arrays of names and descriptions
var Names:[String] = []
var Desctiptions: [String] = []
inital method
override func viewDidLoad() {
super.viewDidLoad()
getRestauraunt()
//create slides
scrollView.delegate = self
slides = createSlides()
setupSlideScrollView(slides: slides)
}
func getRestauraunt(){
let db = Firestore.firestore()
db.collection("Test").getDocuments { (snapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in snapshot!.documents {
let name = document.get("Name") as! String
let description = document.get("Description") as! String
//print("Names: ",name," Description: ",description)
self.Names.append(name)
self.Desctiptions.append(description)
}
}
}
}
create slides method
func createSlides() -> [Slide] {
//firebase link
let slide1:Slide = Bundle.main.loadNibNamed("Slide", owner: self, options: nil)?.first as! Slide
slide1.labelTitle.text = Names[0]
}
I would like if someone could show me how to get the 'createSlides()' method to wait until the 'getRestauraunts()' method has finished. Thank you
Just call it from the end of the getrestaurant()'s getDocuments closure
func getRestauraunt(){
//as before...
} else {
for document in snapshot!.documents {
let name = document.get("Name") as! String
let description = document.get("Description") as! String
self.Names.append(name)
self.Desctiptions.append(description)
}
self.createSlides()
}
}
}
As an aside, it might also be worth creating a simple Document struct with name and description properties, and just having the one array: [Document]

Error adding Firestore documents to Pickerview

I've recently changed a lot on my iOS application and now I got stuck.
I'm trying to insert data from Firestore which looks like this:
So, as you can see I've 6 different names in here.
And here is the code to insert into pickerView.
func getPerson()
{
let authentication = Auth.auth().currentUser?.uid
db.collection("users").document(authentication!).collection("person").getDocuments { (QuerySnapshot, err) in
//If error is not equal to nil
if err != nil
{
print("Error getting documents: \(String(describing: err))");
}
//Succeded
else
{
//For-loop
for _ in QuerySnapshot!.documents
{
//Cleaning the array for the new values
self.personArray.removeAll()
let document = QuerySnapshot!.documents
let data = document.data() //HERE IS THE ERROR
data.forEach { (item) in
if let person1Data = data["name"] as? String
{
self.personArray.append(person1Data)
print(self.personArray)
}
}
}
self.pickerView.reloadAllComponents()
}
}
}
I'm getting the error:
Value of type '[QueryDocumentSnapshot]' has no member 'data'
It used to have QuerySnapshot!.documents.first
but it does not work anymore when I've changed the Firestore data.
Edit:
So. the output is now:
["Joche"] ["Joche", "Joche"] ["Putte"] ["Putte", "Putte"] ["Rebecca"]
["Rebecca", "Rebecca"] ["Fredrik"] ["Fredrik", "Fredrik"] ["Anna"]
["Anna", "Anna"] ["Vickan"] ["Vickan", "Vickan"]
which means it adds everything but x3. How to solve this problem?
data is an instance method of a single QueryDocumentSnapshot not an array , You need
self.personArray.removeAll()
for elem in querySnapshot!.documents {
let data = elem.document.data()
data.forEach {
if let person1Data = $0["name"] as? String {
self.personArray.append(person1Data)
print(self.personArray)
}
}
}

altering a variable outside a closure

I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)

Resources