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
}
Related
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.
What am I doing wrong? I don't get this notification. I have this function:
#objc func onAutocorrection (_ notification: Foundation.Notification) {
Swift.print("\(notification)")
}
later in the same class I do use it as follows:
NotificationCenter.default.addObserver(
self,
selector: #selector(onAutocorrection(_:)),
name: NSSpellChecker.didChangeAutomaticCapitalizationNotification,
object: nil)
The addObserver is executed, but the function is never called even when the application is capitalising in an NSTextView.
Why? Many thanks in advance!
It looks like I misunderstood the notification. It is not meant to be triggered when automatic capitalisation happens but when the systems preference of your Mac is changing.
See the comment of ever helpful Willeke and see Notification of autocorrect
In order to get to the intended result of reacting to autocapitalisation did I implement this function in the NSTextViewDelegate:
public func textView(_ view: NSTextView, didCheckTextIn range: NSRange, types checkingTypes: NSTextCheckingTypes, options: [NSSpellChecker.OptionKey : Any] = [:], results: [NSTextCheckingResult], orthography: NSOrthography, wordCount: Int) -> [NSTextCheckingResult] {
if !range.contains(0){
return results
}
var newResult = [NSTextCheckingResult]()
for result in results {
if let textToChange = view.string[range].components(separatedBy: " ").first, let replacement = result.replacementString?.components(separatedBy: " ").first {
let firstLetterCap = textToChange.capitalizingFirstLetter()
if replacement == firstLetterCap {
continue //don't add to results
}
}
newResult.append(result)
}
return newResult
}
This function will prevent that the first character will be capitalised.
Ultimately, I check whether the capitalised version of the first word of the range that must include position "0" is equal to the first word of the replacement string. And if it is then I remove that result/suggestion from the result list.
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) }
}
}
}
}
The problem I'm having is that my property's willSet and didSet are being called even though I'm only reading the property, and this breaks my app.
I condensed my problem into a playground. Uncomment #1 to see the problem, or #2 to see the expected behavior.
What's going on here?
protocol Departure
{
var line: String? { get }
}
class MyDeparture : Departure
{
var line: String? = "SomeString"
}
#if true
// #1: this causes a write to tableContet later (!)
typealias TableSection = (title: String, rows: [Departure])
#else
// #2: this doesn't cause a write to tableContent later
typealias TableSection = (title: String, rows: [MyDeparture])
#endif
var tableContent: [TableSection] = [ TableSection(title: "MySectionTitle", rows: [ MyDeparture() ]) ]
{
willSet { print("willSet tableContent") }
didSet { print("didSet tableContent") }
}
func getDepartureDescription() -> String? {
print("start getDepartureDescription")
defer { print("end getDepartureDescription") }
#if true
// writes to tableContent in case #1
let lineNumber = tableContent[0].rows[0].line
#else
// never writes to table content
let row = tableContent[0].rows[0]
let lineNumber = row.line
#endif
return "Your line is \(lineNumber)"
}
getDepartureDescription()
This prints
start getDepartureDescription
willSet tableContent
didSet tableContent
end getDepartureDescription
I'm using Xcode 7 (7A218) GM seed. Everything worked as expected in Xcode 6.4 and Swift 1.2.
Side note:
At first I thought that the runtime was--on reading TableSection.rows--creating a new [Departure] array from the [MyDeparture] array that was assigned to it. But even correcting for that in the most explicit way I could think of didn't get rid of the problem:
// More correct types makes no difference:
var departures: [Departure] {
var result = Array<Departure>()
result.append(MyDeparture())
return result
}
var tableContent: [TableSection] = [ TableSection(title: "MyTitle", rows: departures ) ]
There's some sort of lazy initialisation going on. It's only when it gets to the line
let lineNumber = tableContent[0].rows[0].line
that the run time seems to be filling in the contents of the array.
If you declared the array as containing elements that conform to the Departure protocol, the runtime does not know how big the elements of tableContent actually are because both classes and structs can conform to Departure. Hence, I think it is recreating the array and triggering didSet and willSet erroneously.
I tried limiting your protocol to classes like so:
protocol Departure: class
{
var line: String? { get }
}
and the problem goes away because now the runtime knows the array can only contain class references.
I think this is a bug and you should raise it with Apple.
I have spent all day trying to get useable results from NSURL.getResourceValue for NSURLTagNamesKey in swift. The function should take the path name as a string and return an array of strings for the user tags. I have a version of this that works in Objective C, but have not been able to re-write in Swift.
This is the current version of the code:
func listTags(filePath:String)->[String]{
//convert path string to NSURL
let theURL : NSURL = NSURL.fileURLWithPath(filePath)!
//get tags for NSURL -- should be NSArray of NSStrings hiding in an AnyObject?
var tags : AnyObject?
var anyError: NSError?
tags = theURL.getResourceValue(&tags, forKey:NSURLTagNamesKey, error: &anyError)
//unwrap tags object? This part never works
let tagArray = tags! as [AnyObject]
//insert every item in tag array into results array as a String
var results = [String]()
for object in tagArray{
results.append(object as String)
}
return results
}
The code will compile but breaks when it tries to convert the AnyObject to any other type. I have tried every combination I can think of -- [AnyObject], [String], NSArray, with/without exclamation points and question marks.
Am on verge of giving up on Swift.
You're going to kick yourself...
The method getResourceValue:forKey:error returns a value - a Bool, indicating whether the container you passed in as the first argument has been populated. Unfortunately you're assigning the value of this boolean to tags - your container! - which means that whatever was passed in to this container by Cocoa is immediately over-written. This worked for me...
var tags : AnyObject?
var anyError: NSError?
var success = theURL.getResourceValue(&tags,
forKey:NSURLTagNamesKey,
error: &anyError)
if success {
println("container contents \(tags as [String])") // -> [AutoLayout, Swift]
}
With Swift 2, getResourceValue(:forKey:) returns () throws, i.e., void type which throws errors, so the answer above will no longer work. It needs to be wrapped in a do {try} catch{} construction without the anyError variable:
do {
try theURL.getResourceValue(&tags, forKey: NSURLTagNamesKey)
return tags as! [String]
} catch {
// process the error here
}