conformity requirements for userInfo object of UILocalNotification - swift

Using Swift-2.2,
I would like to pass a 'struct' or a 'class object' to userInfo of a UILocalNotification. (see code-illustration below).
Can you tell me how this struct needs to be changed in order to be conform to the requirements of the UserInfo ?
I read something about
a) UserInfo can't be a struct (but I also tried with a class - it did not work either)
b) "plist type" conformity --> but how would I do so ?
c) "NSCoder" and "NSObject" conformity --> but how would I do so ?
The error message I get running the code below is:
"unable to serialize userInfo"
Thank you for any help on this.
struct MeetingData {
let title: String
let uuid: String
let startDate: NSDate
let endDate: NSDate
}
let notification = UILocalNotification()
notification.category = "some_category"
notification.alertLaunchImage = "Logo"
notification.fireDate = NSDate(timeIntervalSinceNow: 10)
notification.alertBody = "Data-Collection Request!"
// notification.alertAction = "I want to participate"
notification.soundName = UILocalNotificationDefaultSoundName
let myData = MeetingData(title: "myTitle",
uuid: "myUUID",
startDate: NSDate(),
endDate: NSDate(timeIntervalSinceNow: 10))
// that's where everything crashes !!!!!!!!!!!!!!
notification.userInfo = ["myKey": myData] as [String: AnyObject]

As the documentation for UILocalNotification.userInfo says:
You may add arbitrary key-value pairs to this dictionary. However, the keys and values must be valid property-list types; if any are not, an exception is raised.
You'll need to convert your data to this type yourself. You might want to do something like this:
enum Keys {
static let title = "title"
static let uuid = "uuid"
static let startDate = "startDate"
static let endDate = "endDate"
}
extension MeetingData {
func dictionaryRepresentation() -> NSDictionary {
return [Keys.title: title,
Keys.uuid: uuid,
Keys.startDate: startDate,
Keys.endDate: endDate]
}
init?(dictionaryRepresentation dict: NSDictionary) {
if let title = dict[Keys.title] as? String,
let uuid = dict[Keys.uuid] as? String,
let startDate = dict[Keys.startDate] as? NSDate,
let endDate = dict[Keys.endDate] as? NSDate
{
self.init(title: title, uuid: uuid, startDate: startDate, endDate: endDate)
} else {
return nil
}
}
}
Then you can use myData.dictionaryRepresentation() to convert to a dictionary, and MeetingData(dictionaryRepresentation: ...) to convert from a dictionary.

Related

Added property to struct which in Swift - invalidates existing objects

I am new to Swift, but have some basic experience with Objective-C programming, and Swift seems much simpler.
However, I can't quite understand the struct thing. I followed a tutorial on how to use Firebase Realtime Database, and this tutorial were using a model to store the data.
But when I modified the struct with additional properties, the previously saved entries in the database is not showing up. I think it's because the model doesn't recognize the object in the database because it has different properties, but how can I make a property optional? So that old entries in the database with different structure (missing properties) are still valid and showing up?
Here is the model. The new property added is all the references to the description.
import Foundation
import Firebase
struct InsuranceItem {
let ref: DatabaseReference?
let key: String
let name: String
let timestamp: Int
let itemValue: Int
let description: String?
let userId: String?
init(name: String, timestamp: Int, itemValue: Int = 0, description: String = "", userId: String, key: String = "") {
self.ref = nil
self.key = key
self.name = name
self.timestamp = Int(Date().timeIntervalSince1970)
self.itemValue = itemValue
self.description = description
self.userId = userId
}
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: AnyObject],
let name = value["name"] as? String,
let timestamp = value["timestamp"] as? Int,
let itemValue = value["itemValue"] as? Int,
let description = value["description"] as? String,
let userId = value["userId"] as? String else { return nil }
self.ref = snapshot.ref
self.key = snapshot.key
self.name = name
self.timestamp = timestamp
self.itemValue = itemValue
self.description = description
self.userId = userId
}
func toAnyObject() -> Any {
return [
"name": name,
"timestamp": timestamp,
"itemValue": itemValue,
"description": description!,
"userId": userId!
]
}
}
The problematic bit is your failable init, init?(snapshot: DataSnapshot). You fail the init even if an Optional property is missing, which is incorrect. You should only include the non-Optional properties in your guard statement, all others should simply be assigned with the optional casted value.
init?(snapshot: DataSnapshot) {
guard
let value = snapshot.value as? [String: Any],
let name = value["name"] as? String,
let timestamp = value["timestamp"] as? Int,
let itemValue = value["itemValue"] as? Int else { return nil }
self.ref = snapshot.ref
self.key = snapshot.key
self.name = name
self.timestamp = timestamp
self.itemValue = itemValue
// Optional properties
let description = value["description"] as? String
let userId = value["userId"] as? String
self.description = description
self.userId = userId
}
Unrelated to your question, but your toAnyObject function is unsafe, since you are force-unwrapping Optional values. Simply keep them as Optionals without any unwrapping and add as Any to silence the warning for implicit coersion.
func toAnyObject() -> Any {
return [
"name": name,
"timestamp": timestamp,
"itemValue": itemValue,
"description": description as Any,
"userId": userId as Any
]
}

How can I filter by specific date from realm object in swift?

I have a realm object with date property type of Date , and want to get list of items with specific date.
If I click specific date from calendar, for example 2020-03-06 , then it will present list of items which was created in 2020-03-06.
:: EDITED ::
Here is my realm object named "Profile" and there are dates from
2020-03-05 to 2020-03-08 .
Here is my Profile object and ProfileManager Singleton.
class Profile: Object {
#objc dynamic var date: Date!
#objc dynamic var content: String!
convenience init(_ content: String) {
self.init()
self.content = content
self.date = Date()
}
}
class ProfileManager {
static let shared = ProfileManager()
private var realm = try! Realm()
var profileList: Results<Profile>?
private init() {
profileList = realm.objects(Profile.self)
}
func save(_ object: Profile) {
do {
try realm.write {
realm.add(object)
}
} catch {
print(error)
}
}
func addNewProfile(_ content: String) {
let newProfile = Profile(content)
save(newProfile)
}
}
And lastly, here is a viewController which has to buttons. One for
adding new Profile, and one for printing filtered profile list.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func addProfilePressed(_ sender: Any) {
ProfileManager.shared.addNewProfile("profile content")
}
#IBAction func filterButtonPressed(_ sender: Any) {
let stringDate = "2020-03-09"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let searchDate:Date = dateFormatter.date(from: stringDate)!
let results = ProfileManager.shared.profileList!.filter("date == %#", searchDate)
print(searchDate)
print(results)
for profile in results {
print(profile.content!)
}
}
}
the result on the console, when filterButtonPressed method called.
2020-03-08 15:00:00 +0000
Results<Profile> <0x7f9b36f160a0> (
)
How can I fix this problem?
And here is another problem.
I set to 'stringDate' a value of "2020-03-09"
but when I print converted date 'searchDate' , it prints "2020-03-08"
why this happens?
Hope now my questions is more clear to understand.
My original answer is below which, after a lot of research was only somewhat correct.
The actual answer has to do with the timestamp portion of the date.
So... if we create a date object using the below code and set it to a known date,
let stringDate = "2020-03-08"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let searchDate:Date = dateFormatter.date(from: stringDate)!
the actual object will look like this
2020-03-08T05:00:00.000Z
However, how the Profile object is being created is like this
convenience init(_ content: String) {
self.init()
self.content = content
self.date = Date()
}
and that date object looks like this
2020-03-08T16:10:25.123Z
so as you can see, if we filter for a specific date these are not equal
2020-03-08T05:00:00.000Z != 2020-03-08T16:10:25.123Z
which is why this
let stringDate = "2020-03-08"
let searchDate:Date = dateFormatter.date(from: stringDate)!
let searchResults = realm.objects(Profile.self).filter("date == %#", searchDate)
could not find the date because it's filtering for this
2020-03-08T05:00:00.000Z
To fix, change the profile class with a date stamp with a default time stamp
class Profile: Object {
#objc dynamic var date: Date!
#objc dynamic var content: String!
convenience init(_ content: String) {
self.init()
self.content = content
let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateFormat = "MM/dd/yy"
let today = Date()
let s = formatter.string(from: today)
let d = formatter.date(from: s)
self.date = d
}
}
or, store your dates as a string yyyymmdd which will remove the ambiguity completely.
-- ORIGINAL ANSWER BELOW ---
Filtering by date is fully supported on date objects. Here's two quick examples. One for filtering for a specific date (for your question) and one for a date range using BETWEEN.
Note, I have a function makeDate that casts a string to a date object. This example uses a Realm DogClass object that has a dog_birthdate Date property.
This filters for objects with a specific date
let searchDate = self.makeDate(fromString: "06/01/2019")
let specificDateResults = realm.objects(DogClass.self)
.filter("dog_birthdate == %#", searchDate)
for dog in specificDateResults {
print(dog.dog_name)
}
This filters for objects within a date range
let startDate = self.makeDate(fromString: "06/01/2019")
let endDate = self.makeDate(fromString: "06/20/2019")
let dateRangeResuls = realm.objects(DogClass.self)
.filter("dog_birthdate BETWEEN {%#,%#}", startDate, endDate)
for dog in dateRangeResuls {
print(dog.dog_name)
}
EDIT: Using the code in the comment from the OP for testing
let stringDate = "2019-06-01"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let searchDate:Date = dateFormatter.date(from: stringDate)!
let result = realm.objects(DogClass.self).filter("dog_birthdate == %#", searchDate)
for dog in result {
print(dog.dog_name)
}
which works perfectly.

Inherit from EKCalendarItem

Because EKEvent can not hold extra properties, I was thinking of creating my own class Event and inherit from EKCalendarItem (same as EKEvent class).
But I get a FrozenClass error, which is quite new to me. Does anybody have any idea of what that means? EKCalendarItem is an open class, so as far as I know I should be able to inherit from that. Or... am I wrong here?
The exact error:
'+[MyApp.Event frozenClass]: unrecognized selector sent to class
0x105667068'
My code:
class Event: EKCalendarItem {
// MARK: - Properties
var id: String
var startDate: Date
var endDate: Date
var isAllDay: Bool
// MARK: - Inits
init?(id: String, dictionary: [String: Any]) {
guard
let title = dictionary["title"] as? String,
let startDate = dictionary["startDate"] as? Timestamp,
let endDate = dictionary["endDate"] as? Timestamp,
let isAllDay = dictionary["isAllDay"] as? Bool
else { return nil }
self.id = id
self.startDate = startDate.dateValue()
self.endDate = endDate.dateValue()
self.isAllDay = isAllDay
super.init()
self.location = dictionary["location"] as? String
self.title = title
self.notes = dictionary["notes"] as? String
}
convenience init?(snapshot: QueryDocumentSnapshot) {
self.init(id: snapshot.documentID, dictionary: snapshot.data())
}
}

Convert NSDate to Date

this might be a stupid question, but I canĀ“t find the information. I'm using CoreData in my app, and I save an array of structs. The problem is when I fetch and try to restore it into the struct array, I have a problem with my Date variable; I can't find a way to convert it from NSDate to Date, I try using as Date, but it makes me force downcast and I'm not sure if it's safe. Is it correct? or is there another way?
This is my Struc:
struct MyData {
var value = Int()
var date = Date()
var take = Int()
var commnt = String()
}
This is how I'm fetchin the data:
func fetchRequestInfo() -> [MyData] {
let fetchRequest: NSFetchRequest<GlucoseEvents> = GlucoseEvents.fetchRequest()
do {
let searchResults = try DatabaseController.getContext().fetch(fetchRequest)
for result in searchResults as [GlucoseEvents] {
let value = Int(result.value)
let date = result.date as Date
let take = Int(result.take)
let commnt = String(describing: result.commnt)
let data = MyData(value: value, date: date, take: take, commnt: commnt)
self.dataArray.append(data)
}
} catch {
print ("error: \(error)")
}
let orderArray = self.dataArray.sorted(by: { $0.date.compare($1.date) == .orderedAscending})
return orderArray
}
And this is the how I set the properties of my CoreDataClass:
#NSManaged public var commnt: String?
#NSManaged public var date: NSDate?
#NSManaged public var value: Int16
#NSManaged public var take: Int16
result.date is an optional NSDate, so you can bridge it
to an optional Date:
result.date as Date?
Then use optional binding to safely unwrap it. In your case that
could be
guard let date = result.date as Date? else {
// date is nil, ignore this entry:
continue
}
You might also want to replace
let commnt = String(describing: result.commnt)
with
guard let commnt = result.commnt else {
// commnt is nil, ignore this entry:
continue
}
otherwise you'll get comment strings like Optional(My comment).
(Rule of thumb: String(describing: ...) is almost never what you
want, even if the compiler suggests it to make the code compile.)
Just make implicit casting like:
let nsdate = NSDate()
let date = nsdate as Date
You can use a function or just an extension:
let nsDate = NSDate()
let date = Date(timeIntervalSinceReferenceDate: nsDate.timeIntervalSinceReferenceDate)

Definition of Global Variable Resetting

I have a class designed to take the temperature data from an API for a specific date and add it to a dictionary. The URL for the API is stored in a global variable called baseURL. It is defined at the beginning as an empty string, but is later changed. My class is below:
import UIKit
import Foundation
typealias ServiceResponse = (JSON, NSError?) -> Void
class WeatherManager: NSObject {
var baseURL: String = ""
var data: String = ""
static let sharedInstance = WeatherManager()
func getRandomUser(onCompletion: (JSON) -> Void) {
println("Starting getRandomUser")
let route = self.baseURL
println(self.baseURL)
makeHTTPGetRequest(route, onCompletion: { json, err in
onCompletion(json as JSON)
})
}
func makeHTTPGetRequest(path: String, onCompletion: ServiceResponse) {
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let json:JSON = JSON(data: data)
onCompletion(json, error)
if error != nil {
println("No Error")
} else {
println("Error")
}
})
task.resume()
}
func addData() {
WeatherManager.sharedInstance.getRandomUser { json in
var jsonData = json["response"]["version"]
self.data = "\(jsonData)"
dispatch_async(dispatch_get_main_queue(),{
let alert = UIAlertView()
alert.title = "Weather Data Update"
if self.data != "null" {
println("Value:\(self.data)")
alert.message = "The weather data was updated successfully."
alert.addButtonWithTitle("OK")
alert.show()
} else {
println("Error Reading Data")
println(self.data)
alert.message = "HealthTrendFinder encountered an error while updating data."
alert.addButtonWithTitle("OK")
alert.show()
}
})
}
}
func updateWeatherHistory() {
println(self.baseURL)
let calendar: NSCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
println("Weather Updating...")
// This sets the start date to midnight of the current date if no start date has been set.
if StorageManager.getValue(StorageManager.StorageKeys.WeatherStartDate) == nil {
let startDate: NSDate = calendar.startOfDayForDate(NSDate())
StorageManager.setValue(startDate, forKey: StorageManager.StorageKeys.WeatherStartDate)
}
// This adds a data array if it hasn't been created yet.
if StorageManager.getValue(StorageManager.StorageKeys.WeatherData) == nil {
StorageManager.setValue([:], forKey: StorageManager.StorageKeys.WeatherData)
}
var weatherData: [NSDate: NSObject] = StorageManager.getValue(StorageManager.StorageKeys.WeatherData)! as! [NSDate : NSObject]
let startMidnight: NSDate = StorageManager.getValue(StorageManager.StorageKeys.WeatherStartDate) as! NSDate
let currentMidnight: NSDate = calendar.startOfDayForDate(NSDate())
let daysFromStartDate: Int = calendar.components(NSCalendarUnit.CalendarUnitDay, fromDate: startMidnight, toDate: currentMidnight, options: nil).day
println("Starting Loop")
for i: Int in 0..<daysFromStartDate {
let dateToBeExamined: NSDate = calendar.dateByAddingUnit(NSCalendarUnit.CalendarUnitDay, value: i, toDate: startMidnight, options: nil)!
if weatherData[dateToBeExamined] == nil {
let calendarUnits: NSCalendarUnit = .CalendarUnitDay | .CalendarUnitMonth | .CalendarUnitYear
let components = NSCalendar.currentCalendar().components(calendarUnits, fromDate: dateToBeExamined)
var month: String
var day: String
if components.month < 10 {
month = "0\(components.month)"
} else {
month = "\(components.month)"
}
if components.day < 10 {
day = "0\(components.day)"
} else {
day = "\(components.day)"
}
var dateString = "\(components.year)\(month)\(day)"
self.baseURL = "http://api.wunderground.com/api/91e65f0fbb35f122/history_\(dateString)/q/OR/Portland.json"
println(self.baseURL)
var get: () = WeatherManager.sharedInstance.addData()
println(get)
weatherData[dateToBeExamined] = self.data
// There is no data for the NSDate dateForInspection. You need to pull data and add it to the dictionary.
} else {
// Data exists for the specified date, so you don't need to do anything.
}
}
println("Loop has finished or been skipped")
}
}
The problem is, baseURL reverts to an empty string when getRandomUser is executed, after baseURL is set to the URL. Why is this happening, and how do I fix it?
Your code is unnecessarily complex, making it hard to diagnose the problem without more information. But here is a suggestion:
Try making it impossible to instantiate more than one instance of your WeatherManager singleton:
class WeatherManager {
private static let _sharedInstance = WeatherManager()
private init() { super.init() }
static func sharedInstance() -> WeatherManager {
return _sharedInstance
}
}
When you are working from outside WeatherManager, you access it by calling:
let wm = WeatherManager.sharedInstane()
Then, when you are working inside WeatherManager, make sure that all your references are to self - i.e., self.baseURL = ... or self.updateWeatherHistory(), instead of WeatherManager.sharedInstance.baseURL = ..., etc.
Though your code is complicated, I think what is going on is you actually have two instances of WeatherManager in play. You are setting the value of baseURL on one, but not the other. If you want it to be a singleton, you need to make it impossible to create more than one.