How would I display a text that shows an array in SwiftUI - xcode

I have an array of dates
let Dates = ["2021-01-07","2021-01-19"]
and I would like to display them in a Text as a sting
Text("\(Dates)")
However I keep getting the error
Cannot call value of non-function type '[String]'.
I wanted to know if this doing this could be achieved in swiftUI.
I also wanted to know if I could display todays date in the format
YYYY-MM-DDT00:00:00Z.

In Swift, normally variable names are lowercased:
let dates : [String] = ["2021-01-07","2021-01-19"]
To display these as-written in Text, you need to turn [String] into String. One possibility is:
Text(dates.joined(separator: ", "))
If you want them in a different format, you need to convert from String to Date:
struct ContentView: View {
#State private var formatterIn = DateFormatter()
#State private var formatterOut = ISO8601DateFormatter()
let dates : [String] = ["2021-01-07","2021-01-19"]
var datesToNewFormat : String {
formatterIn.dateFormat = "yyyy-MM-dd"
return dates.compactMap { formatterIn.date(from: $0) }.map { formatterOut.string(from: $0)}
.joined(separator: ", ")
}
var body: some View {
VStack {
Text(dates.joined(separator: ", "))
Text(datesToNewFormat)
}
}
}
Note on this last item that it deals with timezone conversion as well. In your example, if you want T00:00:00Z, the easiest thing would be to just append that onto the end of your original strings:
dates.map { $0 + "T00:00:00Z" }
Or, you could use DateComponents to manually set the hour to zero. This all probably depends on where your input is coming from and what your intent is with the output.
It is also probably worth mentioning that SwiftUI has some built-in tools for displaying Date in Text. See https://www.hackingwithswift.com/quick-start/swiftui/how-to-format-dates-inside-text-views

I wanted to know if this doing this could be achieved in swiftUI.
Definitely!
struct ContentView: View {
let dates = ["2021-01-07", "2021-01-19"]
var body: some View {
VStack {
Text("Today is \(todayDate())")
ForEach(dates, id: \.self) { date in
Text("\(date)")
}
}
}
func todayDate() -> String {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-DDT00:00:00Z"
let today = Date() /// Date() is the current date
let todayAsString = dateFormatter.string(from: today)
return todayAsString
}
}
Result:
Note that "YYYY-MM-DDT00:00:00Z" date format results in 2021-04-1170. You probably want something like 2021-04-27T11:56:55-0700 instead. In that case, do
dateFormatter.dateFormat = "YYYY-MM-d'T'HH:mm:ssZ"
The '' encloses custom characters that you want to add, like T. For the other format characters, check out this website.

Related

Keystroke lag when typing in Textfield in Table or List

I'm creating a document-based application where data is represented by TextFields in a TableView (it could also be a List, the same issue occurs). When the app SwiftUI app on an Intel MacBook Air, I get a lot of keyboard lag whenever there are more than a dozen rows in my table. It's present on the Apple Studio too, but less noticeable. I've tried changing the table into a List and LazyVStack, but it doesn't seem to make much difference. Using the Swift UI instrument, it looks to me like every TextField on the page is being redrawn on every keystroke, even though their values haven't changed.
I also tried using a custom TextField with a debounce added in (with this as a starting point). This works well for reducing the lag, but I don't think this is how debouncing is intended to be used and I ended up with some strange behaviour.
I suspect that it is rather the case that I've misunderstood how to using #Binding variables in a Document Based application, or possibly I have misconfigured the Struct where I store the data. So here are the essential parts of my code, which will hopefully make it clear where I have gone wrong without having to run anything.
struct ClaraApp: App {
#StateObject var globalViewModel = GlobalViewModel()
var body: some Scene {
DocumentGroup(newDocument: ClaraDocument(claraDoc:GroupVocab())) { file in
MainContentView(data: file.$document)
.environmentObject(self.globalViewModel)
}
}
struct MainContentView: View {
#Binding var data: ClaraDocument // Binding to the document
#EnvironmentObject var globalViewModel : GlobalViewModel
#StateObject var viewModel: ViewModel = ViewModel()
var body: some View {
HostingWindowFinder { window in
if let window = window {
self.globalViewModel.addWindow(window: window)
print("New Window", window.windowNumber)
self.globalViewModel.addViewModel(self.viewModel, forWindowNumber: window.windowNumber)
window.becomeKey()
}
}
.frame(width:0, height: 0)
VStack{
TabView(selection: $viewModel.activeTab){
VocabView(vocabs: $data.claraDoc.vocabs, selectedVocab: $viewModel.selectedVocab)
.tabItem{
Label("Vocabulary", systemImage: "tablecells")
}
.tag(TabType.vocab)
//more tabs here
}
}
}
}
struct VocabView: View{
#Binding var vocabs: [Vocab] // Binding to just the part of the document concerned by this view
#Binding var selectedVocab: Vocab.ID?
var body: some View{
VStack(){
VocabTable(vocabs: $vocabs, selection: $selectedVocab)
.padding([.top, .leading, .trailing])
HStack{
Button("-"){
if selectedVocab != nil{
let oldSelectionIndex = vocabs.firstIndex(where: {$0.id == selectedVocab!})
if oldSelectionIndex != nil{
if oldSelectionIndex! > 0{
selectedVocab = vocabs[oldSelectionIndex! - 1].id
} else {
selectedVocab = nil
}
vocabs.remove(at: oldSelectionIndex!)
}
}
}
.disabled(selectedVocab == nil)
Text("\(String(vocabs.count)) entries")
Button("+"){
let newVocab = Vocab(id: UUID(), word: "", def: "", trans: "", visNote: "", hidNote: "", link: Link(linked: false), date: Date())
vocabs.append(newVocab)
selectedVocab = newVocab.id
}
}
}
}
}
struct VocabTable: View{
#Binding var vocabs: [Vocab]
#Binding var selection: Vocab.ID?
var body: some View{
Table($vocabs, selection: $selection){
TableColumn("Word") {vocab in
TextField("Word", text: vocab.word)
}
TableColumn("Definition") {vocab in
TextField("Definition", text: vocab.def)
}
TableColumn("Translation") {vocab in
TextField("Translation", text: vocab.trans)
}
TableColumn("Visible note") {vocab in
TextField("Visible note", text: vocab.visNote)
}
TableColumn("Hidden note") {vocab in
TextField("Hidden note", text: vocab.hidNote)
}
TableColumn("Created") {vocab in
HStack{
Text(vocab.date.wrappedValue, style: .date)
Text(vocab.date.wrappedValue, style: .time)
}
}
}
.onDeleteCommand{
if selection != nil{
let oldSelectionIndex = vocabs.firstIndex(where: {$0.id == selection!})
if oldSelectionIndex != nil{
if oldSelectionIndex! > 0{
selection = vocabs[oldSelectionIndex! - 1].id
} else {
selection = nil
}
vocabs.remove(at: oldSelectionIndex!)
}
}
}
}
}
// vocab struct which is contained as an array [Vocab] inside the GroupVocab struct
struct Vocab: Identifiable, Codable, Equatable, Hashable {
let id: UUID
var word: String
var def: String
var trans: String
var visNote: String
var hidNote: String
var date: Date
init(id: UUID = UUID(), word: String? = "", def: String? = "", trans: String? = "", visNote: String? = "", hidNote: String? = "", date: Date? = Date()){
self.id = id
self.word = word ?? ""
self.def = def ?? ""
self.trans = trans ?? ""
self.visNote = visNote ?? ""
self.hidNote = hidNote ?? ""
self.date = date ?? Date()
}
static func == (lhs: Vocab, rhs: Vocab) -> Bool {
return lhs.id == rhs.id && lhs.word == rhs.word && lhs.def == rhs.def && lhs.trans == rhs.trans && lhs.visNote == rhs.visNote && lhs.date == rhs.date
}
}
struct GroupVocab: Codable{
var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
var groupName: String = ""
var vocabs = [Vocab]()
var learners = [Learner]()
var lessons = [Lesson]()
var startDate = Date()
var endDate = Date()
}
If that doesn't shed any light, here's my attempt at making a minimal example. It isn't nearly as laggy as the actual app, but from what I can tell it exhibits the same problems. Of course, it could be something in my actual app, which is not present in this minimal example, that I have overlooked. For example, I know that the menu bar is redrawn when editing the document, but removing the menu bar doesn't improve performance. So, I'm assuming that the reduced (albeit still present) lag is due to the general baggage of the program and not one specific element that I haven't taken into account. Obviously if there are no obvious problems in the above, I will need to go back and check everything again, but to my knowledge I have already tried removing and readding each part of the application individually to no avail.
Finally, this is what the actual app looks like in use:
As mentioned in the comments on original post.
TL;DR; For those encountering a similar lag issue, the solution here was to replace the declaration of Vocab as a struct with the use of an ObservableObject class, i.e. Vocab's definition becomes class Vocab: ObservableObject, Identifiable, Codable, Equatable.
Might also want to have a look at https://www.hackingwithswift.com/books/ios-swiftui/adding-codable-conformance-for-published-properties if #Published properties in the class have to be Codable
In a bit more detail
When struct Vocab is used each keystroke (because it is a value type) creates a new (original data + change) instance of the Vocab struct.
The problem [0] with this struct new instance is that SwiftUI cannot detect the singular property change and trigger just updating its corresponding TextField [1].
Instead SwiftUI handles each new keystroke driven struct instance as if it is a completely unrelated Vocab object; for which it has to update every TextField in the Table's entry row.
It's the updating of all of the TextFields in the entry row that causes the perceived lag.
By contrast the solution - using an ObservableObject class - enables binding the TextFields to a property on an object where the instance does not change on each keystroke. Consequently SwiftUI is able to detect and update just the individual entry changes.
The final piece in the puzzle is that when using an ObservableObject. The #Published properties that update Views nicely take some extra effort to enable them to conform with the Codeable protocol. For how to add that conformance there is a nice explanation over [here[( https://www.hackingwithswift.com/books/ios-swiftui/adding-codable-conformance-for-published-properties)
Other bits
If running on higher spec machines - or with fewer properties - issues like these can be difficult to spot.
Other approaches might be possible. For instance, if it's practicable within the context of the rest of the app, relaxing Vocab's Equatable compliance [1] might be enough to enable SwiftUI to do something more clever when determining what TextFields need recomputing.
[0] In this context; generally though, preferring value types such as structs is seen as good practice because it reduces the risk of unexpected side-effects.
[1] Possibly this might also be addressable by relaxing the implementation of Equatable conformance on the struct to just being based on id equivalence.

Extracting single String/Int from fetched API data list

I am struggling to extract single values (strings and Int) from the data I fetched from an API. I fetch the data in the form of a list:
class apiCall {
func getRockets(completion:#escaping ([Rockets]) -> ()) {
guard let url = URL(string: "https://api.spacexdata.com/v4/rockets") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let rockets = try! JSONDecoder().decode([Rockets].self, from: data!)
print(rockets)
DispatchQueue.main.async {
completion(rockets)
}
}
.resume()
}
}
Then when I try using it in a View I succeed when using a List to view the values, like it shows the list of the names from the API data. But then when I want to view a single value like this:
import SwiftUI
struct RocketStatistics: View {
#State var rockets: [Rockets] = []
var body: some View {
VStack{
Text(rockets[1].name)
}
.onAppear {
apiCall().getRockets { (rockets) in
self.rockets = rockets
}
}
}
}
struct RocketStatistics_Previews: PreviewProvider {
static var previews: some View {
RocketStatistics()
}
}
It does not even give me an error, but my preview just won't update and keeps crashing.
So my question is how can I extract single values from this API data in List form and use these single values in my whole project?
To keep it simple and make it work first I started just with fetching the "name" from the API:
import Foundation
import SwiftUI
struct Rockets: Codable, Identifiable {
let id = UUID()
let name : String
}
When it all works I would also want to use Integer values from the API in my project, so tips on how to that are also welcome!
Never ever get items by a hard-coded index in a SwiftUI view without any check. When the view is rendered the first time the array is empty and any index subscription will crash.
Always check if the array contains the required number of items. In this case the array must contain at least two items
VStack{
Text(rockets.count > 1 ? rockets[1].name : "")
}

How do I get text to display Coptic Calendar date SwiftUI

I am trying to use the NSCalendar initializer but i'm having trouble understanding the documentation. what I have so far is
struct SwiftUIView: View {
var body: some View {
let Date = NSCalendar.init(calendarIdentifier: .coptic)
Text("\(Date)")
}
}
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView()
}
}
its giving me an error saying
"Value of optional type 'NSCalendar?' must be unwrapped to a value of type 'NSCalendar'"
if someone could help me figure this out, that would be great. Thank you
The interface you want for Swift is Calendar rather than NSCalendar:
let calendar = Calendar(calendarIdentifier: .coptic)
The NSCalendar interface is from ObjC and is documented to return nil if the name of the calendar is unknown (you can pass arbitrary strings in ObjC for this parameter).
The Swift Calendar interface cannot fail because it will not allow you to create an unknown identifier.
There are many types that begin "NS" that are bridged from Objective-C. It is very common for there to be a Swift version that drops the "NS" prefix. When you see this, you should generally use the Swift version. It will generally behave in a more Swift-like way.
If your goal is to display the current date on the Coptic calendar, however, you will need more than this. A calendar represents the whole calendar, not a particular date. You will need a DateFormatter in order to create a localized string.
For example:
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.calendar = Calendar(identifier: .coptic)
dateFormatter.eraSymbols = ["BC", "AM"] // See below
print(dateFormatter.string(from: Date()))
// Baramouda 18, 1737 AM
(I believe there is a bug in Calendar such that the Coptic calendar has era symbols "ERA0" and "ERA1". I believe the correct symbols for this calendar are "BC" and "AM". You can force this by assigning them directly to the date formatter. If I'm correct that this is a bug and it impacts you, I recommend opening an Apple Feedback. See also the DateFormatter documentation for how to customize this string.)
To xTwisteDx's point, to put this in SwiftUI you want something along these lines:
struct ContentView: View {
let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.eraSymbols = ["BC", "AM"] // Workaround for Foundation bug
dateFormatter.calendar = Calendar(identifier: .coptic)
return dateFormatter
}()
func today() -> String {
dateFormatter.string(from: Date())
}
var body: some View {
Text(today())
.padding()
}
}
Ok so this is an issue with Optionals. Anytime you see ? or ! then you can assume that it is an optional. Essentially the reason that it is a Type?, notice the ? is because it's possible that it has returned a nil value. For example the following snippets are possible in Swift
let forceUnwrappedString: String!
let optionalString: String?
let safeString: String = "Hello World!"
print(forceUnwrappedString) //This WILL crash, the variable is nil.
print(optionalString) //This MAY crash, if the value is nil.
print(safeString) //This WON'T crash.
The way that you handle these is to check using if let or guard statements. Alternatively you can also force unwrap after checking for nil.
If Let
In this example, if optionalString is nil, the print statement WILL NOT happen. However if optionalString contains a value, it will. It's essentially a way of telling the compiler to check for nil values and only run code if it's safe.
if let safeString = optionalString {
print(safeString)
}
Guard
In this example, it works the same as an If Let with a notable difference being that the safeString is accessible at a bigger scope than the If Let also it returns if the value is not "unwrapped" so any code below it will not be called if it's not safely unwrapped.
guard let safeString = optionalString { else return }
print(safeString)
Force Unwrapping
Force unwrapping is something you should avoid however there are instances when it's acceptable. This is an example of force unwrapping. Notice that I used the ! which basically says "I know this value is not nil, use it." That is the only time you should ever use it, when you can guarantee there isn't a nil value.
let someString: String?
someString = "Hello World!"
print(someString!)
Your Problem
In the context of your issue, NSCalendar.init(calendarIdentifier: .coptic) returns an optional type or, someType? meaning it has the possibility to be nil The solution is below.
let Date = NSCalendar.init(calendarIdentifier: .coptic)
if let safeDate = Date {
Text("\(safeDate)")
} else {
//The date value was nil, do something else
}

Swift2 access component with string-name

Im more familiar with ActionScript3 and see many similarities in Swift2, kind of why i am trying out basic coding in Swift2 and Xcode.
Here's my example:
#IBOutlet weak var b1CurrSpeed: NSTextField!
I want to store b1CurrSpeed as a string so i could access the actual textfield component to set its default value when application is loaded.
I'm aiming for Swift2 for osx apps.
Here is a fictional example, not related to any actual code:
var tf:NSTextField = this.getItem("b1CurrSpeed");
tf.stringValue = "Hello world";
Reason to this approach is following...
I would like to store textfield value in NSUserDefaults, the key for defaults would be name of that textfield. So when looping thru the defaults, i would like to get key as string and when ive got that i'd have access to actual component to set its stringvalue property.
Tho, is that good approach in Swift / xCode ?
If you want to create a function for it, do someting like this:
func getStringForKey(key: String) -> String {
guard let result = NSUserDefaults.standardUserDefaults().objectForKey(key) as! String else { return "" }
return result
}
You can set the TextFields value with myTextField.text
Swift's Mirror type can get you close to it but it is limited to NSObject subclasses, can only access stored properties and is read-only.
Yet, there are ways around these limitations if your requirements will allow.
For example, here's an extension that will save and restore defaults values for all UITextfields on a view controller using the name you gave to each IBOutlet.
extension UIViewController
{
func loadDefaults(userDefaults: NSUserDefaults)
{
for prop in Mirror(reflecting:self).children
{
// add variants for each type/property you want to support
if let field = prop.value as? UITextField,
let name = prop.label
{
if let defaultValue = userDefaults.objectForKey(name) as? String
{ field.text = defaultValue }
}
}
}
func saveDefaults(userDefaults: NSUserDefaults)
{
for prop in Mirror(reflecting:self).children
{
if let field = prop.value as? UITextField,
let name = prop.label
{
if let currentValue = field.text
{ userDefaults.setObject(currentValue, forKey: name) }
}
}
}
}

How do I view the actual string variable of a variable/constant?

Environment: Debugging Parse return object within Xcode 6.1
I can see the text within the object structure but can't adequately view its assigned variable.
Here's the code:
func retrieveAllMediaFromParse() {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let myData:PFQuery = PFQuery(className: kParseMedia)
myData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]!, error:NSError!)->Void in
if !(error != nil){
// for object in objects {
let myObject = objects[0] as PFObject
let format = myObject.objectForKey("format") as String
let access = myObject.objectForKey("access") as String
let media = myObject.objectForKey("media") as String
let owner = myObject.objectForKey("owner") as String
let text = myObject.objectForKey("text") as String
let thumbNail = myObject.objectForKey("thumbnail") as NSString
}
}
});
}
let text = myObject.objectForKey("text") as String
When I take the 'po' of the command string I get the correct interpretation:
However, when I do the same for the assigned variable (constant), I get the following:
How do I view the variable/constant to display the actual string?
When program is paused in the debugger, you can find the values of PFObject fields by going to the estimatedData line in the debugger.

Resources