I'm having trouble with storing a date that I got from SQLite DB into a Date Var, I'm trying to save the date coming from DB to the ResultTask.AlarmDate after converting it from String to Date.
When I tried and deleted the ? from ResultTask.AlarmDate? it stored the data fine, but when I have a task without an alarm I get an error cause I want to store nil it the var when there's no alarm, and tried multiple other ways to fix it but nothing work.
I made a break point right after the issue and used the Console to see the data and got confused, cause the data shown when I print what I'm trying to store in the var but the var is still nil, all shown below.
Notes
The code that getting data from DB works fine, I'm using the Objective-C FM-Database library, and result is seen in the Console below.
the .toDate() is an extension for String that I use and it code is in AppDelegate.swift as seen bellow, and it work just fine.
the Task Variable type is something I made in a Task.swift file to easy the work, and it work just fine & shown below.
The Function:
func GetTasks() -> [Task]{
var Tasks : [Task] = [Task]()
let dirPaths = NSSearchPathForDirectoriesInDomains(.documentDirectory,.userDomainMask, true)
let docsDir = dirPaths[0]
databasePath = (docsDir + "/" + "Tasks.db") as NSString
let TaskRecorderDB = FMDatabase(path: databasePath as String)
if (TaskRecorderDB?.open())! {
let querySQL = "Select * from 'Tasks' ORDER BY Priority DESC"
let results:FMResultSet? = TaskRecorderDB?.executeQuery(querySQL, withArgumentsIn: nil)
while results!.next() {
var ResultTask : Task! = Task()
ResultTask.ID = Int((results?.int(forColumn: "ID"))!)
ResultTask.FileName = results?.string(forColumn: "FileName")
ResultTask.Name = results?.string(forColumn: "Name")
ResultTask.Categorie = Int((results?.int(forColumn: "Categorie"))!)
ResultTask.Priority = Int((results?.int(forColumn: "Priority"))!)
ResultTask.AlarmDate? = (results?.string(forColumn: "Alarm").toDate())!
//Break point
ResultTask.Repeat? = (results?.string(forColumn: "Repeat"))!
ResultTask.Completed = Int((results?.int(forColumn: "Completed"))!).toBool()
if(ResultTask.Completed == false){
Tasks.append(ResultTask)
}
ResultTask = nil
}
TaskRecorderDB?.close()
} else {
print("Error #DB06: \(TaskRecorderDB?.lastErrorMessage())")
}
return Tasks
}
Console:
(lldb) print (results?.string(forColumn: "Alarm").toDate())!
(Date) $R0 = 2016-11-23 17:21:00 UTC
(lldb) print ResultTask.AlarmDate
(Date?) $R1 = nil
(lldb)
AppDelegate.swift:
extension String {
func toDate () ->Date! {
var FinalDate : Date! = nil
if (self != ""){
let DateFormatter : Foundation.DateFormatter = Foundation.DateFormatter()
DateFormatter.locale = Locale.current
DateFormatter.dateFormat = "y-MM-dd_HH-mm-ss"
FinalDate = DateFormatter.date(from: self)!
}
return FinalDate
}
}
Task.swift:
import Foundation
class Task : NSObject {
var ID : Int! = nil
var FileName : String! = nil
var Name : String! = nil
var Categorie : Int! = nil
var Priority : Int! = nil
var AlarmDate : Date! = nil
var Repeat : String! = nil
var Expired : Bool! = nil
var Completed : Bool! = nil
}
Thanks in advance.
Edit for #KaraBenNemsi :
I updates the .toDate() as you said.
& in task I used var AlarmDate : Date? instead of var AlarmDate : Date! = nil (I didn't use you're other tip in Task.swift for now cause I'll need to change a lot of other code in the app).
And used ResultTask.AlarmDate = results?.string(forColumn: "Alarm").toDate() and I get a fatal error: unexpectedly found nil while unwrapping an Optional value in it when the task doesn't have an alarm.
Try to change this line in the task class since it can be optional
from this
var AlarmDate: Date! = nil
to this
var AlarmDate: Date?
And then use this line to set the alarm
ResultTask.AlarmDate = results?.string(forColumn: "Alarm")?.toDate()
Also variable names should usually start with a lowercase letter.
I also think this line is unnecessary. You can just omit that line.
ResultTask = nil
Please also change the extension to
extension String {
func toDate() -> Date? {
guard self != "" else {
return nil
}
var finalDate: Date?
let DateFormatter : Foundation.DateFormatter = Foundation.DateFormatter()
DateFormatter.locale = Locale.current
DateFormatter.dateFormat = "y-MM-dd_HH-mm-ss"
finalDate = DateFormatter.date(from: self)
return finalDate
}
}
And maybe try to get more into how optionals work
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TheBasics.html
Addition:
Your class task should probably also look more like below. But then you have to change code in other places as well. It also forces you to pass all the required parameters in the constructor when initialising the task object.
class Task {
var id: Int
var fileName: String
var name: String
var category: Int
var priority: Int
var alarmDate: Date?
var repeating: String
var expired: Bool
var completed: Bool
init(id: Int, fileName: String, name: String, category: Int, priority: Int, repeating: String, expired: Bool, completed: Bool) {
self.id = id
self.fileName = fileName
self.name = name
self.category = category
self.priority = priority
self.repeating = repeating
self.expired = expired
self.completed = completed
}
}
And here is also a link to a Swift style guide:
https://github.com/raywenderlich/swift-style-guide
Change your task to have this
var AlarmDate : Date?
And then do this
if let alarm = results?.string(forColumn: "Alarm").toDate() {
ResultTask.AlarmDate = alarm
}
If any nil is found while unwrapping results, you won't set the value of ResultTask.AlarmDate.
Related
I have various types of Worksheet structs that I use to populate a view, where the user fills out entries and these are then saved.
So far I've just used each struct individually, but I realized it would be more efficient to have a main 'Worksheet' struct that holds the common properties (id, date) so that I can reuse some views rather than creating a different view for every single type of Worksheet.
For example, here is how I read data of a certain worksheet:
if let diaryArray = vm.readData(of: [Diary](), from: "diary_data") {
let sortedArray = diaryArray.sorted(by: {$0.date > $1.date})
vm.diaries = sortedArray
}
I'd like this to be something like
if let diaryArray = vm.readData(of: [T](), from: "\(path)_data") {
let sortedArray = diaryArray.sorted(by: {$0.date > $1.date})
vm.worksheets = sortedArray
}
So, I won't know what type of worksheet it is yet, but that it is a worksheet that has a date property for sure, so I can sort it by that, with this approach I could just reuse this view and function instead of having to write this for every type.
I've tried two ways so far to achieve this with my structs:
firstly, using protocols:
protocol Worksheet: Codable, Identifiable, Hashable {
var id: String { get set }
var date: Date { get set }
}
extension Worksheet {
static func parseVehicleFields(id: String, date: Date) -> (String, Date) {
let id = ""
let date = Date()
return (id, date)
}
}
struct Diary: Worksheet {
var id: String
var date: Date
var title: String
var situation: String
var thoughts: String
var emotions: String
var physicalSensations: String
var behaviours: String
var analysis: String
var alternative: String
var outcome: String
init() {
self.id = ""
self.date = Date()
self.title = ""
self.situation = ""
self.thoughts = ""
self.emotions = ""
self.physicalSensations = ""
self.behaviours = ""
self.analysis = ""
self.alternative = ""
self.outcome = ""
}
}
struct Goal: Worksheet {
var mainGoal: String
var notificationActive: Bool
var notificationDate: Date
var obstacles: String
var positiveBehaviours: String
var immediateActions: String
var goalAchieved: Bool
init() {
self.mainGoal = ""
self.notificationActive = false
self.notificationDate = Date()
self.obstacles = ""
self.positiveBehaviours = ""
self.immediateActions = ""
self.goalAchieved = false
}
}
It seems close, but I get the error of 'Type 'any Worksheet' cannot conform to 'Decodable''
when trying this:
if let array = vm.readData(of: [any Worksheet](), from: "\(path)_data") {
let sortedArray = array.sorted(by: {$0.date > $1.date})
vm.worksheets = sortedArray
}
Here is the save function for reference:
func saveWorksheetProtocol<T: Worksheet>(for type: [T], to path: String) {
let directoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
let documentURL = (directoryURL?.appendingPathComponent(path).appendingPathExtension("json"))!
let jsonEncoder = JSONEncoder()
let data = try? jsonEncoder.encode(type)
do {
try data?.write(to: documentURL, options: .noFileProtection)
} catch {
print("Error...Cannot save data!!!See error: \(error.localizedDescription)")
}
}
It works if I use Diary as the parameter, but that defeats the point of trying to make this Generic to Worksheets, not their specific types.
Alternatively, I tried a method that creates a major Struct type of Worksheet, that contains the other types. This actually works nicely, except I have to hold empty data for EVERY type of worksheet, if I want to create an array of only 'Diary' entries for example, and this seems redundant and inefficient:
struct Worksheet: Codable, Identifiable, Hashable {
var id: String
var date: Date
var diary: Diary2
var goal: Goal2
var experiment: Experiment2
init() {
self.id = ""
self.date = Date()
self.diary = Diary2()
self.goal = Goal2()
self.experiment = Experiment2()
}
}
struct Diary: Codable, Hashable {
var title: String
var situation: String
var thoughts: String
var emotions: String
var physicalSensations: String
var behaviours: String
var analysis: String
var alternative: String
var outcome: String
init() {
self.title = ""
self.situation = ""
self.thoughts = ""
self.emotions = ""
self.physicalSensations = ""
self.behaviours = ""
self.analysis = ""
self.alternative = ""
self.outcome = ""
}
}
struct Goal: Codable, Hashable {
var mainGoal: String
var notificationActive: Bool
var notificationDate: Date
var obstacles: String
var positiveBehaviours: String
var immediateActions: String
var goalAchieved: Bool
init() {
self.mainGoal = ""
self.notificationActive = false
self.notificationDate = Date()
self.obstacles = ""
self.positiveBehaviours = ""
self.immediateActions = ""
self.goalAchieved = false
}
}
So here I can just find a specific 'Worksheet', sort it, and then later access the values of whichever one it is, i.e. worksheet.diary.title, worksheet.goal.title etc. But this means each entry has redundant values of every other, and I tried making each type optional, but I could not then use them with bindings, as I was getting unresolvable unwrap errors, if the type i.e. 'Diary?' was optional in the Worksheet struct:
TextField("", text: $entry.diary!.title, axis: .vertical) <- error would be here, even when checking if diary exist first.
.lineLimit(20)
.disabled(!editMode)
So I am looking for a way to handle my Worksheet types more efficiently, so that I can reduce repetitive code for functions and views where possible.
I would like to receive the date value in the api value as utc. I looked up the stackoverflow.
There was a similar case, but we couldn't solve it because it was different from me.
The server (POSTMAN(db) stores the value "b_date": 1602813891.
link >> Dateformatter issue in swift
mycode
var ViewItem: BoardView?
func DetailViewList() {
DispatchQueue.main.async {
self.txtUserName.text = String(ViewItem.userId ?? "")
self.txtCount.text = String(ViewItem.b_count ?? 0)
}
}
func utcToLocal(utcDate: String, dateFormat: String) -> String {
let dfFormat = DateFormatter()
dfFormat.dateFormat = dateFormat
dfFormat.timeZone = TimeZone(abbreviation: "UTC")
let dtUtcDate = dfFormat.date(from: utcDate)
dfFormat.timeZone = TimeZone.current
dfFormat.dateFormat = dateFormat
txtDate.text = Int(ViewItem?.b_date) // ERROR [Cannot invoke initializer for type 'Int' with an argument list of type '(Int?)'] , Overloads for 'Int' exist with these partially matching parameter lists: (CGFloat), (Double), (Float), (Float80), (Int64), (Word), (__shared NSNumber)
return dfFormat.string(from: dtUtcDate!)
}
jsonData
struct BoardView: Codable {
var b_date: Int?
var b_count: Int?
var userId: String?
}
I have a project I'm working on where I need to perform a search using the same terms in Core Data and to construct an URL. Rather than dump a bunch of code in my DataManager class, I'm attempting to create a separate search term class what will store the elements of the query and construct a NSCompoundPredicate and an URL when it's initialized.
I'm using Mirror to construct a dictionary with keys and values of the class. I'm attempting to filter out non-nil values from the dictionary. I do not understand how to apply solutions elsewhere on the site to my problem.
The only non-optional vars for the class are the Dictionary, the URL, and the NSCompoundPredicate. So when the class is initialized, I run a method to populate the dictionary, which in turn is used to set set up a NSCompoundPredicate and a URL.
Where I'm running into difficulty is filtering out non-nil values from a dictionary:
func setNonNilDictionary() {
// get the keys for the class' properties
let keys = Mirror(reflecting: self).children.flatMap{$0.label}
// get the values for the class' properties
let values = Mirror(reflecting: self).children.flatMap{$0.value}
// zip them into a dictionary
var propertyDict = Dictionary(uniqueKeysWithValues: zip(keys, values))
// remove the nil values
let filteredDict = propertyDict.filter{$0.value != nil}
nonNilPropertyDict = filteredDict
print(nonNilPropertyDict)
}
When I print the nonNilPropertyDict, it's still got ****ing nil keys in it. I've looked around at a few different solutions on SO, but I keep running into the same problem regardless of what I try.
What am I missing and how do I fix it?
Here's what my class looks like:
class LastSearch: NSObject {
var startDate: Date?
var endDate: Date?
var minMagnitude: Double?
var maxMagnitude: Double?
var minLongitude: Double?
var maxLongitude: Double?
var minLatitude: Double?
var maxLatitude: Double?
var minDepth: Double?
var maxDepth: Double?
// methods to create predicate and url reference this dictionary
var nonNilPropertyDict: Dictionary<String, Any>!
var url: URL!
var predicate: NSCompoundPredicate!
init(startDate: Date?, endDate: Date?,
minMagnitude: Double?, maxMagnitude: Double?,
minLongitude: Double?, maxLongitude: Double?,
minLatitude: Double?, maxLatitude: Double?,
minDepth: Double?, maxDepth: Double?) {
super.init()
// Dates
self.startDate = startDate
self.endDate = endDate
// Magnitude Values
self.minMagnitude = minMagnitude
self.maxMagnitude = maxMagnitude
// Geographic Coordinates
self.minLongitude = minLongitude
self.maxLongitude = maxLongitude
self.minLatitude = minLatitude
self.maxLatitude = maxLatitude
// Depth Values
self.minDepth = minDepth
self.maxDepth = maxDepth
self.setNonNilDictionary()
self.setURL()
self.setPredicate()
}
func setNonNilDictionary() {
let keys = Mirror(reflecting: self).children.flatMap{$0.label}
let values = Mirror(reflecting: self).children.flatMap{$0.value}
let nonNilPropertyDict = Dictionary(uniqueKeysWithValues: zip(keys, values))
print(filtered)
print(nonNilPropertyDict)
}
}
You can't directly compare the children's value to nil because it of type Any but you can use pattern matching to make sure it isn't nil while iterating through the children:
func setNonNilDictionary() {
var nonNilProperties = [String: Any]()
for child in Mirror(reflecting: self).children {
guard let label = child.label else { return }
if case Optional<Any>.some(let value) = child.value {
nonNilProperties[label] = value
}
}
nonNilPropertyDict = nonNilProperties
}
I'm trying to learn swift and the concept of optionals is destroying me.
I have the code below and it keeps crashing when trying to create an Event object b/c eventDescription is finding a nil value. How can I get past this error and create my Event object with a description that is either nil or an empty string?
EventDao.getUpcoming() {
(upcomingEvents, error) -> Void in
if(upcomingEvents != nil) {
// Loop through all the events
for currentEvent in upcomingEvents! {
print(currentEvent)
let name = currentEvent["name"] as! NSString
let start = currentEvent["start"] as! NSString
let teamId = currentEvent["team_id"] as! Int
let eventDescription = currentEvent["description"] as? String
let eventId = currentEvent["id"] as! Int
// Format date format
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ"
let date = dateFormatter.dateFromString(start as String)
dateFormatter.dateStyle = NSDateFormatterStyle.ShortStyle
dateFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
let formattedDate = dateFormatter.stringFromDate(date!)
self.getTeamInfo(teamId) {
(team, err) -> Void in
self.getMyResponseForEvent(eventId) {
(response, err) -> Void in
print(eventDescription)
var event: Event
if (response == nil) {
event = Event(name: name as String, team: team!, time: formattedDate, description: eventDescription! as! String, myResponse: "No Response") // ERRORS HERE WITH: fatal error: unexpectedly found nil while unwrapping an Optional value
} else {
...
print(currentEvent outputs:
{
"default_response" = {
id = 1;
label = "No Response";
};
description = "<null>";
end = "2016-01-19T19:00:43.000-06:00";
id = 1966;
location = Withers;
name = "Game 5 VS cat.png";
start = "2016-01-19T18:00:43.000-06:00";
"team_id" = 193;
timezone = "America/Chicago";
}
This is my Event.swift file:
import Foundation
class Event {
// MARK: Properties
var name: String
var team: Team
var time: String
var description: String?
var myResponse: String
// MARK: Initialization
init(name: String, team: Team, time: String, description: String, myResponse: String) {
self.name = name
self.team = team
self.time = time
self.description = description
self.myResponse = myResponse
}
}
Try something like this:
if let t = team where response == nil {
event = Event(name: name as String, team: t, time: formattedDate, description: eventDescription ?? "", myResponse: "No Response")
} else...
One of your optionals was nil. Either team! was nil, or eventDescription! was nil. You can use the "nil coalescing operator" ?? to try to unwrap eventDescription and substitute a blank string if it is nil.
BTW, you shouldn't need to conditionally cast eventDescription to String again, since you already did that when you created it, in this line:
let eventDescription = currentEvent["description"] as? String
At that point, it is either a String or it is nil. You don't need to cast it again.
Just about every time you see the error unexpectedly found nil while unwrapping an Optional value it is because you are force unwrapping an optional with !.
Hey I just want to save my NSMutableArray with Class. But When i try to read the file my app getting crush. I found this and i tried to convert for NSMutableArray. Now I cant figure out what can i do.
My Class:
class Customer {
var name = String()
var email = String()
var phoneNumber = Int()
var bAdd = String()
var bAdd2 = String()
var sAdd = String()
var sAdd2 = String()
init(name: String, email: String, phoneNumber: Int, bAdd: String, bAdd2: String, sAdd: String, sAdd2: String) {
self.name = name
self.email = email
self.phoneNumber = phoneNumber
self.bAdd = bAdd
self.bAdd2 = bAdd2
self.sAdd = sAdd
self.sAdd2 = sAdd2
}
class func exists (path: String) -> Bool {
return NSFileManager().fileExistsAtPath(path)
}
class func read (path: String) -> NSMutableArray? {
if Customer.exists(path) {
return NSMutableArray(contentsOfFile: path)!
}
return nil
}
class func write (path: String, content: NSMutableArray) -> Bool {
return content.writeToFile(path, atomically: true)
}
}
My Array:
var ItemData:NSMutableArray = NSMutableArray()
and My Read Code:
let documents = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
let customerPath = documents.stringByAppendingPathComponent("Test.plist")
ItemData = Customer.read(customerPath)!
When i tried to read im gettin this crash:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Any Advice?
Your code works perfectly fine except that you do not actually have anything to read from in the beginning, therefore Customer.read(customerPath) returns nil, which you try to unwrap - therefore the error.
If you just write something beforehand and then try to read it again like this, everything works fine.
ItemData.addObject("aisnd")
Customer.write(customerPath, content: ItemData)
ItemData = Customer.read(customerPath)!
Of course that is not the way to actually do it, because it is normal that you don't have anything present in the beginning. Therefore you have to check wether or not the read function actually returns something useful:
var ItemData:NSMutableArray = NSMutableArray()
if let item = Customer.read(customerPath) {
ItemData = item
} else {
print("no value found")
}
Final note 1 because playground suggested it: remove the ! from the as! String. Final note 2: don't start variable names uppercase, the variable should be called itemData.
Edit
To write Customer objects you have to do something like this.