I’ve problem with fatalError(). I try to adding to a list of words but in this line there is problem it says:
fatal Error in ContentView.swift
and i don’t why this happened my code was totally correct.
The code of this error:
// id were are * here* then there was a problem - trigger a cradh and report the error
fatalError("could not load start.txt from bundle.")
And the whole code is;
import SwiftUI
struct ContentView: View {
#State private var usedWords = [String]()
#State private var rootWord = ""
#State private var newWord = ""
var body: some View {
NavigationView {
List {
Section {
TextField("enter your word", text: $newWord)
.textInputAutocapitalization(.never)
}
Section {
ForEach(usedWords, id: \.self) { word in
HStack {
Image(systemName: "\(word.count).circle")
Text(word)
}
}
}
}
.navigationTitle(rootWord)
.onSubmit(addNewWord)
.onAppear(perform: startGame)
}
}
func addNewWord() {
// lowercase and trim the word, to make sure we don:t add duplicate words with case differences
let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines)
// exit if the remaining string is empty
guard answer.count > 0 else { return }
// extra validation to come
withAnimation {
usedWords.insert(answer, at: 0)
}
newWord = ""
}
func startGame() {
// 1.find the URL for start.txt in our app bundle
if let startWordsURL = Bundle.main.url(forResource: "start",withExtension: "txt") {
// 2.load start.txt into a string
if let startWords = try? String(contentsOf: startWordsURL) {
// 3.split the string up into an array of strings, splitting on line breaks
let allWords = startWords.components(separatedBy: "\n")
// 4.pcik one random word, or use "yamori" as a sensible default
rootWord = allWords.randomElement() ?? "yamori"
// if we are here everything has worked, so. we can exit
return
}
}
// id were are * here* then there was a problem - trigger a cradh and report the error
fatalError("could not load start.txt from bundle.")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Any idea? How to fix that error
Thanks.
The reason for this failing is either the url for "start.txt" is nil or try? String(contentsOf: startWordsURL) fails. In order to help you debug and become an understanding why your current code design is bad consider this design:
func startGame() {
// 1.find the URL for start.txt in our app bundle
guard let startWordsURL = Bundle.main.url(forResource: "start",withExtension: "txt") else{
//assign the default value
rootWord = "yamori"
// if the url is not found this will print in the console
print("File could not be found")
return
}
do{
// 2.load start.txt into a string
let startWords = try String(contentsOf: startWordsURL)
// 3.split the string up into an array of strings, splitting on line breaks
let allWords = startWords.components(separatedBy: "\n")
// 4.pick one random word, or use "yamori" as a sensible default
rootWord = allWords.randomElement() ?? "yamori"
} catch{
//if the file couldn´t be read this will print the error in the console
print(error)
}
}
Related
i'm here to ask help.
I just create a tiny dictionary for self use.
And now i want to add a SearchBar to search the keyword from the CSV file,
and it's not work and i cant find any reference from internet
Here is Preview
My CVS Model Code:
import Foundation
struct Model: Identifiable {
let id: Int
let Vocab: String
let Type1 : String
let Type2 : String
let Meaning: String
let Meaning2: String
init(raw:[String]){
self.id = Int(raw[0])!
self.Vocab = raw [1]
self.Type1 = raw[2]
self.Type2 = raw[3]
self.Meaning = raw[4]
self.Meaning2 = raw[5]
}
}
Handle CSV Code:
import Foundation
func cleanRows(file:String) -> String{
var cleanFile = file
cleanFile = cleanFile.replacingOccurrences(of: "\r", with: "\n")
cleanFile = cleanFile.replacingOccurrences(of: "\n\n", with: "\n")
return cleanFile
}
func loadCSVData() ->[Model]{
var csvToStruct = [Model]()
guard let filePath = Bundle.main.path(forResource: "Book2", ofType:"csv") else {
print("Error: file not found")
return []
}
var data = ""
do{
data = try String(contentsOfFile: filePath)
} catch{
print(error)
return []
}
data = cleanRows(file: data)
var rows = data.components(separatedBy: "\n")
rows.removeFirst()
for row in rows {
let csvColumns = row.components(separatedBy: ",")
if csvColumns.count == rows.first?.components(separatedBy: ",").count{
let linesStruct = Model.init(raw:csvColumns)
csvToStruct.append(linesStruct)
}
}
return csvToStruct
}
Content:
import SwiftUI
struct ContentView: View {
#State var Modelx:[Model]
#State private var searchText = ""
var body: some View{
NavigationView{
List{
ForEach (Modelx){ model in
NavigationLink(destination:{ModelDetailView(thisModel:model)}){
HStack{
Text("#"+String(format: "%03d", model.id))
.font(.subheadline)
Text(model.Vocab)
.font(.headline)
Spacer()
reusableTypeView(thisVocabType: model.Type1)
if model.Type2 != ""{
reusableTypeView(thisVocabType: model.Type2)
}
}
}
}
}.navigationTitle("Music Picionary")
.searchable(text: $searchText)
}
}
}
struct reusableTypeView: View {
var thisVocabType:String
var body:some View{
Text(thisVocabType)
.font(.system(size:15))
.padding(5)
.background(Color(thisVocabType))
.cornerRadius(9)
.foregroundColor(.white)
}
}
I think the main point is in the Handle CSV file right?
how can i add a SearchBar to search the keyword from the CSV file
thanks everyone help~
The problem is you never use the search test. You must say the list what data you want and how to apply the filter set by searchTest.
A example on how to use searchText :
struct ContentView: View {
#State var models:[Model]
#State private var searchText = ""
// use a model list filtered by the searchText
var filteredModels: [Model] {
if searchText == "" {
return models
}
return models.filter { model in
model.meaning.contains(searchText)
}
}
init() {
models = Model.loadCSVData()
}
var body: some View{
NavigationView{
List{
ForEach (filteredModels){ model in
NavigationLink(destination:{ModelDetailView(thisModel:model)}){
HStack{
Text("#"+String(format: "%03d", model.id))
.font(.subheadline)
Text(model.vocab)
.font(.headline)
Spacer()
reusableTypeView(thisVocabType: model.type1)
if model.type2 != ""{
reusableTypeView(thisVocabType: model.type2)
}
}
}
}
}.navigationTitle("Music Picionary")
.searchable(text: $searchText)
.textInputAutocapitalization(.none)
.autocorrectionDisabled()
// here just to suppress autocapitalisation
// and autocorrection (not needed)
}
}
}
Model with its associated functions :
struct Model: Identifiable {
let id: Int
let vocab: String
let type1 : String
let type2 : String
let meaning: String
let meaning2: String
init(raw:[String]){
self.id = Int(raw[0])!
self.vocab = raw [1]
self.type1 = raw[2]
self.type2 = raw[3]
self.meaning = raw[4]
self.meaning2 = raw[5]
}
static func cleanRows(file:String) -> String{
var cleanFile = file
cleanFile = cleanFile.replacingOccurrences(of: "\r", with: "\n")
cleanFile = cleanFile.replacingOccurrences(of: "\n\n", with: "\n")
return cleanFile
}
static func loadCSVData() ->[Model]{
var csvToStruct = [Model]()
guard let filePath = Bundle.main.path(forResource: "Book2", ofType:"csv") else {
print("Error: file not found")
return []
}
var data = ""
do{
data = try String(contentsOfFile: filePath)
} catch{
print(error)
return []
}
data = cleanRows(file: data)
var rows = data.components(separatedBy: "\n")
// compute column number from first row
let titlesRow = rows.removeFirst()
let columnCount = titlesRow.components(separatedBy: ",").count
for row in rows {
let csvColumns = row.components(separatedBy: ",")
if csvColumns.count == columnCount {
let linesStruct = Model.init(raw:csvColumns)
csvToStruct.append(linesStruct)
}
}
return csvToStruct
}
}
Note : I made some little changes in property naming using lowercase for first character. I also initialise the models array in ContentView.init.
I also put the functions inside the model definition as these are specific to your model.
I'm trying to fetch some data from a firebase document and have it displayed in a ListView. Getting the data and decoding it into my own struct works fine (tested by letting the console print it), but it won't show up in the actual ListView. I tried to create 2 example datasets to see if there is anything wrong with the implementation of my struct and/or ListView, but those display just fine.
My best guess is that the ListView isnt happy with the id I get from the firebase document.
Here is how I fetch the data(pls ignore the ugly solution in checkAvailablePkgs() for now, that is going to change):
class firebaseController : ObservableObject {
let databaseFIRB = Firestore.firestore()
#Published var forUserAvailablePkgs : [DLPackage] = []
func checkAvailablePkgs() {
if Auth.auth().currentUser != nil {
var allAccessiblePkgs : [String] = []
let userUID = Auth.auth().currentUser!.uid
databaseFIRB.collection("user-access").document(userUID).getDocument { (document, error) in
if let document = document, document.exists {
let dataDescription = document.data().map(String.init(describing:)) ?? "nil"
let pkgArr = dataDescription.components(separatedBy: ", ")
for partString in pkgArr {
var tmpStringSlicing : String
tmpStringSlicing = partString.replacingOccurrences(of: "\": <null>", with: "")
tmpStringSlicing = tmpStringSlicing.replacingOccurrences(of: "[\\[\\]\"]", with: "", options: .regularExpression)
allAccessiblePkgs.append(tmpStringSlicing)
}
self.checkIndividualPkgInformation(pkgIDs: allAccessiblePkgs)
} else {
print("Document does not exist")
}
}
}
}
func checkIndividualPkgInformation(pkgIDs: [String]) {
for id in pkgIDs {
databaseFIRB.collection("download-pkgs").document(id).getDocument { (document, error) in
if let document = document, document.exists {
let result = Result {
try document.data(as: DLPackage.self)
}
switch result {
case .success(let pkg):
if let pkg = pkg {
// A `Package` value was successfully initialized from the DocumentSnapshot.
print("Package: \(pkg)")
self.forUserAvailablePkgs.append(pkg)
} else {
// A nil value was successfully initialized from the DocumentSnapshot,
// or the DocumentSnapshot was nil.
print("Document does not exist")
}
case .failure(let error):
// A `City` value could not be initialized from the DocumentSnapshot.
print("Error decoding package: \(error)")
}
} else {
print("Package-Document does not exist")
}
}
}
}
}
This is the struct I want to save it as:
struct DLPackage: Identifiable, Codable {
#DocumentID var id = UUID().uuidString
var name : String = ""
var size : String = ""
var contentpaths : [String] = [""]
}
And here is the ListView that refuses to Display the aquired data:
struct FIRBauthView: View {
let firebaseCtrl: firebaseController = firebaseController()
var body: some View {
VStack() {
List(firebaseCtrl.forUserAvailablePkgs) {item in
HStack() {
Text(String(item.name)).font(.custom("Avenir", size: 26))
Spacer()
Text(String(item.size)).font(.custom("Avenir", size: 26))
}
}
Button {
firebaseCtrl.checkAvailablePkgs()
} label: {
Text("Check")
}
}
}
}
I can give my firebaseControler().forUserAvailablePkgs an initial value of:
forUserAvailablePkgs.append(contentsOf: [DLPackage(name: "Example", size: "1GB", contentpaths: ["hello","something"]),DLPackage(name: "Nr2", size: "1GB", contentpaths: ["hello","something else"])])
and they will show up just fine.
This is my print from the firebase data loaded into the forUserAvailablePkgs Array:
[Light_Player.DLPackage(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("CDB68E0C-DCE2-4FAA-AD55-872322A55E56")), name: "Example", size: "1GB", contentpaths: ["hello", "something"]),
Light_Player.DLPackage(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("DCAAF136-24FE-470A-B897-2D178AEDB3A0")), name: "Nr2", size: "1GB", contentpaths: ["hello", "something else"]),
Light_Player.DLPackage(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("package2")), name: "Bewährte Klassiker", size: "3.4GB", contentpaths: ["placeholder for another path", "placeholder for a different path"]),
Light_Player.DLPackage(_id: FirebaseFirestoreSwift.DocumentID<Swift.String>(value: Optional("package1")), name: "Basic", size: "7.3GB", contentpaths: ["placeholder for path", "placeholder for path 2"])]
I tried changing the id for the struct to:
#DocumentID var id: String?
since I found it in a tutorial online, however the code doesn't copile that way.
I also tried overwriting the ids from the firebase data with generated UUIDs after fetching the data from the database, but I still couldn't get the ListView to display it.
Any help is appreciated.
Well after a good night of sleep I was cured of my temporary blindness and found that I was missing the #ObservedObject property in my view.
struct FIRBauthView: View {
#ObservedObject var firebaseCtrl: firebaseController = firebaseController()
instead of
struct FIRBauthView: View {
let firebaseCtrl: firebaseController = firebaseController()
Now the data is showing up as intended.
I'm trying to set up the #AppStorage wrapper in my project.
I'm pulling Texts from a JSON API (see DataModel), and am hoping to store the results in UserDefautls. I want the data to be fetched .OnAppear and stored into the #AppStorage. When the user taps "Get Next Text", I want a new poem to be fetched, and to update #AppStorage with the newest Text data, (which would delete the past Poem stored).
Currently, the code below builds but does not display anything in the Text(currentPoemTitle).
Data Model
import Foundation
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
public class FetchPoem: ObservableObject {
// 1.
#Published var poems = [Poem]()
init() {
getPoem()
}
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
TestView
import SwiftUI
struct Test: View {
#ObservedObject var fetch = FetchPoem()
#AppStorage("currentPoemtTitle") var currentPoemTitle = ""
#AppStorage("currentPoemAuthor") var currentPoemAuthor = ""
var body: some View {
VStack{
Text(currentPoemTitle)
Button("Fetch next text") {
fetch.getPoem()
}
}.onAppear{
if let poem = fetch.poems.first {
currentPoemTitle = "\(poem.title)"
currentPoemAuthor = "\(poem.author)"
}
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
What am I missing? Thanks.
Here are a few code edits to get you going.
I added AppStorageKeys to manage the #AppStorage keys, to avoid errors retyping key strings (ie. "currentPoemtTitle")
Your question asked how to update the #AppStorage with the data, and the simple solution is to add the #AppStorage variables within the FetchPoem class and set them within the FetchPoem class after the data is downloaded. This also avoids the need for the .onAppear function.
The purpose of using #ObservedObject is to be able to keep your View in sync with the data. By adding the extra layer of #AppStorage, you make the #ObservedObject sort of pointless. Within the View, I added a Text() to display the title using the #ObservedObject values directly, instead of relying on #AppStorage. I'm not sure if you want this, but it would remove the need for the #AppStorage variables entirely.
I also added a getPoems2() function using Combine, which is a new framework from Apple to download async data. It makes the code a little easier/more efficient... getPoems() and getPoems2() both work and do the same thing :)
Code:
import Foundation
import SwiftUI
import Combine
struct AppStorageKeys {
static let poemTitle = "current_poem_title"
static let poemAuthor = "current_poem_author"
}
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
public class FetchPoem: ObservableObject {
#Published var poems = [Poem]()
#AppStorage(AppStorageKeys.poemTitle) var poemTitle = ""
#AppStorage(AppStorageKeys.poemAuthor) var poemAuthor = ""
init() {
getPoem2()
}
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
guard let poemData = data else {
print("No data")
return
}
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
self.updateFirstPoem()
}
} catch {
print("Error")
}
}
.resume()
}
func getPoem2() {
let url = URL(string: "https://poetrydb.org/random/1")!
URLSession.shared.dataTaskPublisher(for: url)
// fetch on background thread
.subscribe(on: DispatchQueue.global(qos: .background))
// recieve response on main thread
.receive(on: DispatchQueue.main)
// ensure there is data
.tryMap { (data, response) in
guard
let httpResponse = response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return data
}
// decode JSON data to [Poem]
.decode(type: [Poem].self, decoder: JSONDecoder())
// Handle results
.sink { (result) in
// will return success or failure
print("poetry fetch completion: \(result)")
} receiveValue: { (value) in
// if success, will return [Poem]
// here you can update your view
self.poems = value
self.updateFirstPoem()
}
// After recieving response, the URLSession is no longer needed & we can cancel the publisher
.cancel()
}
func updateFirstPoem() {
if let firstPoem = self.poems.first {
self.poemTitle = firstPoem.title
self.poemAuthor = firstPoem.author
}
}
}
struct Test: View {
#ObservedObject var fetch = FetchPoem()
#AppStorage(AppStorageKeys.poemTitle) var currentPoemTitle = ""
#AppStorage(AppStorageKeys.poemAuthor) var currentPoemAuthor = ""
var body: some View {
VStack(spacing: 10){
Text("App Storage:")
Text(currentPoemTitle)
Text(currentPoemAuthor)
Divider()
Text("Observed Object:")
Text(fetch.poems.first?.title ?? "")
Text(fetch.poems.first?.author ?? "")
Button("Fetch next text") {
fetch.getPoem()
}
}
}
}
struct Test_Previews: PreviewProvider {
static var previews: some View {
Test()
}
}
I am very new using Swift and SwiftUI and I'm trying to write and iOS app that reads a folder, calculcate hash for each file, and then copy them to an external drive.
For now I'm trying to import one file and calculate its hash. However, I always get the same error, saying I don't have permission to view it.
Here is my code:
//
// ContentView.swift
// FileExporter
//
// Created by adrien le falher on 27/09/2020.
//
import SwiftUI
import CryptoKit
struct ContentView: View {
#State private var document: MessageDocument = MessageDocument(message: "Hello, World!")
#State private var isImporting: Bool = false
#State private var isExporting: Bool = false
#State private var isMoving: Bool = false
var body: some View {
VStack {
GroupBox(label: Text("Message:")) {
TextEditor(text: $document.message)
}
GroupBox {
HStack {
Spacer()
Button(action: { isImporting = true }, label: {
Text("Import")
})
Spacer()
Button(action: { isExporting = true }, label: {
Text("Export")
})
Spacer()
Button(action: { isMoving = true }, label: {
Text("Export")
})
Spacer()
}
}
}
.padding()
.fileExporter(
isPresented: $isExporting,
document: document,
contentType: .plainText,
defaultFilename: "Message"
) { result in
if case .success = result {
// Handle success.
} else {
// Handle failure.
}
}
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.image],
allowsMultipleSelection: false
) { result in
do {
print("ok")
guard let selectedFile: URL = try result.get().first else { return }
guard var fileBytes : String = try hashFile(selectedFile) else { return }
//guard let message = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return }
let message = fileBytes
print(message)
document.message = message
} catch let error{
print(error.localizedDescription)
document.message = error.localizedDescription
}
}
}
}
extension Data {
init(reading input: InputStream) throws {
self.init()
input.open()
defer {
input.close()
}
let bufferSize = 1024
let buffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer {
buffer.deallocate()
}
while input.hasBytesAvailable {
let read = input.read(buffer, maxLength: bufferSize)
if read < 0 {
//Stream error occured
throw input.streamError!
} else if read == 0 {
//EOF
break
}
self.append(buffer, count: read)
}
}
}
func hashFile (_ fileURL : URL) -> String {
print(fileURL)
var hashed = ""
do {
var fileBytes = try Data (contentsOf: fileURL)
//OU reading : InputStream(url: fileURL)!
print("Filebytes is \(fileBytes)")
print("Bytes" + String(fileBytes.base64EncodedString()))
let hashed = SHA256.hash(data: fileBytes)
return String(hashed.description)
} catch let error {
hashed = error.localizedDescription
}
return hashed
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
/*#START_MENU_TOKEN#*/Text("Hello, World!")/*#END_MENU_TOKEN#*/
}
}
It seems that .fileImporter() is new and I am having trouble finding info on it. I don't know if I should (or how) ask permissions to iOS to access files ; the file picker works so I'm not sure what I did wrong.
Any help would be appreciated.
Thank you.
You have to call the method startAccessingSecurityScopedResource() on the URL first, before reading the content of the file. Note: don't forget to call stopAccessingSecurityScopedResource() after you're finished!
You can find more information in the Apple documentation.
Edit:
Here is the code that worked for me:
.fileImporter(
isPresented: $isImporting,
allowedContentTypes: [.plainText],
allowsMultipleSelection: false
) { result in
do {
guard let selectedFile: URL = try result.get().first else { return }
if selectedFile.startAccessingSecurityScopedResource() {
guard let fileContent = String(data: try Data(contentsOf: selectedFile), encoding: .utf8) else { return }
defer { selectedFile.stopAccessingSecurityScopedResource() }
} else {
// Handle denied access
}
} catch {
// Handle failure.
print("Unable to read file contents")
print(error.localizedDescription)
}
}
I have a Swift5 project but i decided that it would be easier to build only one of it's screen with SwiftUI.
The problem is that i don't know anything about SwiftUI so i am sorry if my question is stupid lol.
There is a TextView on the screen and i would like to load the text data from firebase.
My problem is that i can't use the loaded data in the TextView.
Can someone help me how i can use the function's variable in my VStack ( if its even possible )?
I tried putting the function in the VStack but it gave me some error.
Here is my code:
var body: some View {
List {
VStack {
Text("Just some dummy data, this is not important.")
// i want the "text" variable from loadData() to be in this Text
}
}.onAppear {
func loadData() {
guard let userdef = userdefStr(forKey: "didSelect") else { return }
self.db.collection("news").whereField("title", isEqualTo: userdef)
.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
let text = document.data()["text"] as? String ?? ""
// this variable should be in the VStack's Text
print("text: \(text)")
Text(text)
}
}
}
}
// i think this is how i can call the loadData function,maybe i am wrong
loadData()
}
}
The answer for my question is called 'SwiftUI state variables'.
I had to make a new State variable:
#State var text = "Just some dummy data, this is not important."
In the VStack: Text(text)
Ad lastly after the let text = document.data()["text"] as? String ?? "" i put self.text = description