Posts

Post marked as solved
5 Replies
If your data are static, or change only infrequently, putting them into the app is fine. If the data change, and you are distributing the app to others, you will need to replace the app’s data file, recompile, and re-release the app.  There’s a way of using files from the Documents folder of iOS, or “external” files, but this can be a bit of a learning curve. Assuming that your data are static, then here’s what to do: Place your term/phrase csv file into your App’s project folder using MacOS’ Finder, preferably using Copy (not Move), so as to keep a backup copy. In Xcode, select File -> Add Files to….  From the Top Menu and a pop-up screen will appear Select your file from the list (others should be greyed out) and make sure that the Destination - copy files if needed box is ticked and the Add to targets box - YourAppName is also ticked Press the Add button (far right, bottom of pop-up) Back in Xcode’s main screen, replace the getData function in DataModel with: func getData() { // DatFrame(contentsOfCSVFile) needs a URL reference, so we need to get the app's data (Bundle resource) reference as a URL         guard let fileURL = Bundle.main.url(forResource: "TermPhrases", withExtension: "csv") else { // CAUTION - withExtension is case sensitive, so if your CSV file was created as TermPhrases.CSV then you need to use uppercase in the parameter             print("**** error getting file url")             return         }         let csvOptions = CSVReadingOptions(hasHeaderRow: true, ignoresEmptyLines: true, delimiter: ",")         do {             dataTable = try DataFrame(contentsOfCSVFile: fileURL,columns: nil, rows: nil, types: ["Term":CSVType.string,"Phrase":CSVType.string], options: csvOptions)             } catch {                 print("Failed to load datatable from CSV \(error)")                 return         }         print("**** Loaded \(dataTable.rows.count) Terms")  // check to make sure terms got loaded     } I haven't tested this with iOS (just on the Mac), but it should work - assuming that you created a new iOS project or multi-platform project with Shared resources. Best wishes and regards, Michaela
Post marked as solved
4 Replies
Further to my comment on my previous answer, a computed property (realDate) cannot be used as an NSSortDescriptor key (my mistake, sorry) but FetchedResults can be sorted, upon return, using Swift array's .sorted(by: {$0.realDate! > $1.realDate!}) and then used in your table (or whatever), but you have to be careful in converting the String date to a system date, or in dealing with nil values coming from the conversion. In my sample code for realDate, the dateFormat should be df.dateFormat = "MMM dd, yyyy" not df.dateFormat = "MMM d, yyyy". Maybe check your date strings for variations to the string format, just to be sure? I made a couple of typos when creating test data and that messed the solution up!! If you do have variations, you can test for them in the reaDate extension code and convert accordingly. Regards, Michaela
Post not yet marked as solved
2 Replies
Only the last iteration of thing persists because it is recreated on each iteration of i. Change the initialisation of thing to be str, i.e. to "SOME SEARCH TEXT" and the reference to str.map to be thing.map and it will work: let str = "SOME SEARCH TEXT" var thing = str let letters = ["A","E","S"] let replacements = ["a", "e", "s"]  letters.enumerated().forEach { (i,r) in         thing = String(thing.map {$0 == Character(String(r)) ? Character(String(replacements[i])) : $0});     print(thing) } If it's OK to have str mutate, then you can change str to a var and then use this code: var str = "SOME SEARCH TEXT" let letters = ["A","E","S"] let replacements = ["a", "e", "s"]  letters.enumerated().forEach { (i,r) in         str = String(str.map {$0 == Character(String(r)) ? Character(String(replacements[i])) : $0});     print(str) } Best wishes and regards, Michaela
Post marked as solved
4 Replies
There is a way of fixing the problem without restructuring your CoreData model and then recreating the CoreData records. However, this can be a bit tricky if you haven't had experience with manually generating class definitions for CoreData entities. In essence the solution is this: In Xcode's CoreData Model editor, click on the Entity you need to fix, make sure that the Inspector panel is open (far right of Xcode screen) then change the Codegen option to Manual/None (it's probably set at "Class definition"). Then (still in the Model editor) on the Xcode menu bar (far top of screen) select Editor, CreateNSManagedObject Subclass and follow the prompts to create Swift files in your project for the CoreData Entity classes. Create a new Swift file for an extension to your Entity. For example, if your Entity is called "Commitment", create a file called "Commitment_Developer" and then use code like this: import Foundation extension Commitment  {     var realDate : Date? {         guard let strDate = strAttribute else { // where "strAttribute" is the name of your CoreData date-as-string attribute             return nil         }         let df = DateFormatter()         df.timeZone = NSTimeZone.local         df.dateFormat = "MMM d, yyyy"         guard let rDate = df.date(from:strDate) else {             return nil         }         return rDate     } } Then, in your NSSortDescriptor use the "realDate" property that was created in the extension. Apart from that, your code remains the same as now - without any refactoring of your CoreData model or actual data. You also have the ability to use the "realDate" elsewhere whenever you need access to a system date, as opposed to a string. Note that "realDate" is Optional, so needs to be unwrapped upon use. It could be defined as non-optional, if you're really confident in the integrity of the string-dates, but then there's a problem if you later try to use iCloud syncing with non-optionals. I hope this helps. best wishes and regards, Michaela
Post marked as solved
5 Replies
Here's a revised Example App for your situation, where there's now a Search Bar and only search results are shown in the View - not the full set of Terms and Phrases. ContentView import SwiftUI import TabularData struct ContentView: View {     @ObservedObject var model = DataModel.shared     @State private var searchText = ""     var body: some View {         NavigationView{             Text("Searching for \(searchText)")                             .searchable(text: $searchText)             List(model.searchResults.rows,id:\.index) { row in                 HStack{                     Text(row["Term"] as! String)                     Text(String(row["Phrase"] as! String))                 }             }         }         .onChange(of: searchText) { srchText in             model.searchResults = model.findTerm(srchText)         }     } } Data Model import Foundation import TabularData class DataModel : ObservableObject {     static let shared = DataModel()     var dataTable = DataFrame()     @Published var searchResults = DataFrame() init() {         getData()     }     func getData() {         var url: URL?          do {              url = try FileManager.default.url(for: FileManager.SearchPathDirectory.downloadsDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: true)         } catch {               print("Failed to get Downsloads URL \(error)")             return         }                      let csvOptions = CSVReadingOptions(hasHeaderRow: true, ignoresEmptyLines: true, delimiter: ",")         let fileURL = url?.appendingPathComponent("TermPhrases.csv")         do {             dataTable = try DataFrame(contentsOfCSVFile: fileURL!,columns: nil, rows: nil, types: ["Term":CSVType.string,"Phrase":CSVType.string], options: csvOptions)             } catch {                 print("Failed to get load datatable from CSV \(error)")                 return         }     }     func findTerm(_ searchTerm: String) -> DataFrame {         let results = dataTable.filter ({ row in             guard let term = row["Term"] as? String else {                 return false             }             if term == searchTerm {                 return true             }             return false         })         return DataFrame(results)     } } extension DataFrame.Rows : RandomAccessCollection {   } Caution!! The findTerm func checks for an exact match of the Term, i.e. case sensitive and full match. If you need case insensitivity and partial matches, you need to change the findTerm function. If your purpose is only to look up phrases for a Term, then this is pretty much a complete solution, although if you've started an Xcode project with View Controllers (Storyboard) then to use my solution you'll need to create a new project with SwiftUI Interface. Best wishes and regards, Michaela
Post marked as solved
5 Replies
In SwiftUI, ContentView is the main user interface, equivalent to Main ViewController in UIKit. The List command (actually a struct in SwiftUI) produces a Table on the UI. List(model.dataTable.rows,id:\.index) { row in // this creates a Table of entries from dataTable (i.e. Terms and phrases             HStack{ // this contains formatting for the rows of the table                 Text(row["Term"] as! String) // this is the Term "cell", although it's not a cell like in UIKit                 Text(String(row["Phrase"] as! String)) // this is the Phrase cell, although it's not a cell like in UIKit             }         } That's all that's needed in SwiftUI to create a working UI for your Term/Phrase data. However, in Xcode (when creating a project) you have to make sure that the Interface option for a new project specifies SwiftUI not Storyboard. With UIKit, processing of data for a View is normally done within the ViewController of the View. With SwiftUI, the data processing is preferably done in a Data Model with results made available to SwiftUI views via (for example) the var model = DataModel.shared statement. So, for the Search Bar, the query text would be provided to the Data Model, which runs the query, then provides the result(s) back to the SwiftUI View. If I get time later today (it's 8:30am now here in Oz) I'll extend my sample code to include searching. Best wishes and regards, Michaela
Post marked as solved
5 Replies
The answers to your questions depend what you want to do with the "Dictionary", how volatile it is (i.e. updates, additions, deletions) and how large it is. A few hundred Terms that never change could be handled without a database (i.e. without an indexed, structured permanent store such as SQLite or CoreData). From my understanding of what you're looking for, I'd suggest creating your terms/phrases in a spreadsheet (e.g. Excel) and then exporting the table as a CSV file for use with code like in the SwiftUI sample below: ContentView import SwiftUI import TabularData struct ContentView: View {     var model = DataModel.shared     var body: some View {         List(model.dataTable.rows,id:\.index) { row in             HStack{                 Text(row["Term"] as! String)                 Text(String(row["Phrase"] as! String))             }         }     } } Data Model - this is not a database: it's where the importing and processing of the terms takes place within the app, and the data have to be loaded into the app again when next run. import Foundation import TabularData class DataModel {     static let shared = DataModel()      @Published var dataTable = DataFrame()     init() {         getData()         let searchTerm = "Nine"         print("there are \(findTerm(searchTerm).rows.count) terms for \(searchTerm)")     }     func getData() {         var url: URL?           do {              url = try FileManager.default.url(for: FileManager.SearchPathDirectory.downloadsDirectory, in: FileManager.SearchPathDomainMask.userDomainMask, appropriateFor: nil, create: true)         } catch {               print("Failed to get Downsloads URL \(error)")             return         }                      let csvOptions = CSVReadingOptions(hasHeaderRow: true, ignoresEmptyLines: true, delimiter: ",")         let fileURL = url?.appendingPathComponent("TermPhrases.csv")                do {             dataTable = try DataFrame(contentsOfCSVFile: fileURL!,columns: nil, rows: nil, types: ["Term":CSVType.string,"Phrase":CSVType.string], options: csvOptions)             } catch {                 print("Failed to load datatable from CSV \(error)")                 return         }     }     func findTerm(_ searchTerm: String) -> DataFrame.Slice {         let results = dataTable.filter ({ row in             guard let term = row["Term"] as? String else {                 return false             }             if term == searchTerm {                 return true             }             return false         })         return results     } } extension DataFrame.Rows : RandomAccessCollection { } Thiis sample uses the TabularData Framework which has many features for importing and manipulating data from CSV files (spreadsheet output), but also for Machine Learning. This sample app was written for MacOS using csv input from the Downloads folder: Sample Data in a file called TermPhrases.csv Term Phrase Nine A stitch in time saves these Two A bird in the hand is worth these in the bush Seven Pillars of wisdom The findTerm function in the DataModel shows how to filter (Search) the Data Table for a term. DataFrame filtering is not like normal Swift array filtering. Hopefully this gets you started, then you can decide what other processing you'd like to do on the Data Table (i.e. your dictionary) Best wishes and regards, Michaela
Post marked as solved
1 Replies
My understanding is that CMAmbientPressureData is an internal class, which is then used by CMAltimeter to calculate relative altitude based on pressure changes. The data from CMAltimeter.startRelativeAltitudeUpdates has a pressure property that provides the atmospheric pressure, as an NSNumber of kilopascals, of the reported relative altitude. As of iOS 15 there is a CMAbsoluteAltitudeData class that provides the absolute altitude (and changes thereof), but I can't see a pressure property available via the updates handler. I've been using CMAltimeter data on a regular basis for a couple of years in my running app and the update handler consistently provides an update every second, irrespective of an altitude (or pressure) change or not. Most of my runs start and end at the same location, so I often see a discrepancy of 2 to 5 metres between the start and end relative altitudes - which coincide with the atmospheric pressure change at that location as recorded by the Bureau of Meteorology. I haven't accessed the pressure property of CMAltitudeData but, from the foregoing, I conclude that it would be a fairly accurate recording of the current ambient pressure of a location. For my purposes, the CMAltimeter data are far more accurate and consistent than the altitude data from CoreLocation. I hope this helps. Best wishes and regards, Michaela
Post marked as solved
1 Replies
I haven't used UIKit since SwiftUI arrived, but the below code should work. Create a DataModel class for use as a singleton and put your items array into it: import Foundation class DataModel {     static let shared = DataModel()     var items: [String] = [] } In your FirstViewController add a reference to the DataModel singleton, then use the items array as needed, e.g. in your UITableView let dataModel = DataModel.shared Use dataModel.items in the UITableView In the SecondViewController also add a reference to the DataModel singleton, then use this to append to items: class SecondViewController: UIViewController { let dataModel = DataModel.shared let textField: UITextField = UITextField() let saveButton: UIButton = UIButton() @objc func saveButtonTapped() { dataModel.items.append(textField.text) } } However, using this method you then, presumably, need to force a reload of the FirstViewController's tableview. This can be done via delegation (which could also be used to send the new item for appending in the FirstViewController) or via Combine. If using Combine, the DataModel becomes: import Foundation import Combine class DataModel {     static let shared = DataModel()     let itemAdded = PassthroughSubject<(String), Never>()         var items: [String] = [] {         didSet {             itemAdded.send("ItemAdded")         }     } } and the FirstViewController needs to include Combine and to have var itemSink : AnyCancellable? = nil itemSink = dataModel.itemAdded.sink(receiveValue: { message in // you don't need to use message, but can be useful if you want to know about additions, deletions etc by sending different messages             tableView.reloadData()         }) at an appropriate place in your code, i.e. to force the Table Reload. I haven't tested this with a real UIKit app and tableview, but it should work. Regards, Michaela
Post not yet marked as solved
1 Replies
I'm not sure that I fully understand what you're doing and how you're doing it. However, I've a couple of apps that have numerous variables in my Data Store (backend?) updated by async methods (e.g. BLE devices) and prefer not to overload my SwiftUI Views with extraneous updates. I therefore use the Combine Framework to publish an object when it's ready for display e.g. in my Data Store  public let transactionAdded = PassthroughSubject<(Transaction), Never>(). Then when appropriate, in my Data Store code, I publish the object e.g. transactionAdded.send(newTransaction) In my SwiftUI View I use .onReceive to listen for the published object and do whatever is necessary e.g. .onReceive((dataStore.transactionAdded), perform: { transaction in .......}. The perform usually needs to trigger a View refresh, e.g. by setting/updating a @State variable. That said, Combine can update values in the store without triggering view updates, and/or chain async results before then informing views. Dunno if this helps. Cheers, Michaela
Post not yet marked as solved
1 Replies
I assume that you want to show the location's coordinates as a place name: if so, you need to use CoreLocation's CLGeocoder class, which returns an array of Placemarks. i.e. name and address. Usually there will be only one place mark for a CLLocation (i.e. coordinates), but sometimes there can be several, e.g. when a location has a number of businesses. There are plenty of examples on the Web of how to use CLGeocoder, but if you get stuck I can provide some sample code. Best wishes and regards, Michaela
Post not yet marked as solved
1 Replies
Ah, I've just discovered why my Transaction entity was giving problems: SwiftUI uses a Transaction struct to pass an animation between views in a view hierarchy. It's too late to change my entity name, and the above mentioned solution works anyway. Cheers, Michaela
Post not yet marked as solved
1 Replies
Could you explain a bit more about what you're trying to achieve and what you mean by "inclusive help"? Also, are you able to say more about @MasEmpathyCommunity (assuming that it is a community-based programme)? I'm a retired IT Professional (50+ years experience) and still developing apps, using latest Apple technology, but mainly for my own / family / friends use, or for a worthwhile public app, e.g. symptom tracker for a rare cancer. I don't claim to be an Apple expert, but can usually figure things out as needs be from reading this forum and Stack Overflow. I'd be happy to assist you, if I'm able, once I've better understood your needs. Best wishes, Michaela (in Australia)
Post marked as solved
1 Replies
The problem is in the initialisation of your @Published var pp: [[CGFloat]] = [[0]], because this is initialising pp with a single 0 value - to which you append tuples in updateP. Changing it to @Published var pp = [[CGFloat]]() fixes the problem. If you want to start with [0,0], then change the initialisation of pp to be @Published var pp: [[CGFloat]] = [[0,0]] Best wishes and regards, Michaela
Post not yet marked as solved
3 Replies
Here's an example of ForEach with an enum: The enum enum WordLength : Int, CaseIterable {     case random = 0     case five = 5     case six = 6     case seven = 7 } The ForEach ForEach(WordLength.allCases,id:\.self) { wordLength in         Text(String(describing:wordLength).capitalized)  } The enum doesn't have to be Int: I've taken this example from an app that uses the numeric value (Int) in processing, whereas in the ForEach it's for showing Picker options as text (string). Regards, Michaela