Local notifications used to fire for me in the simulator until I basically rewrote my app using a different data model. Now, even though everything seems to be happening that needs to happen for a notification to fire, it's not.
Here's what I can confirm show up in the logs, in order:
"User has notifications permissions enabled"
"User enabled notifications permissions"
"Attempting to create alarm with time 19:37"
"Creating notification for day: 19, time: 19:36, with uuid=B76AA489-CF41-49CB-9C3D-CF48590A9933"
"There are 1 notificationUuids attached to the alarm created"
"----- notificationUuids: -----"
"uuid: B76AA489-CF41-49CB-9C3D-CF48590A9933"
No errors are printed. This leads me to believe that the alarm is getting created properly, and the notification is getting added to the notification center, but it's not firing for some reason. Below is my code, abridged to reduce unneeded complexity. I only removed things that aren't related to notifications.
class AlarmTableViewController: UITableViewController {
//MARK: Public properties
var alarms = [Alarm]()
let ALARM_CELL_IDENTIFIER = "AlarmTableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
requestUserNotificationsPermissionsIfNeeded()
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(didReceiveNotification), name: NotificationNames.randomAlarmNotification, object: nil)
loadAlarms()
for alarm in self.alarms {
os_log("There are %d notifications for alarm %d", log: OSLog.default, type: .debug, alarm.notificationUuids.count, alarm.alarmNumber)
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: NotificationNames.randomAlarmNotification, object: nil)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: ALARM_CELL_IDENTIFIER, for: indexPath) as? AlarmTableViewCell else {
fatalError("The dequeued cell is not an instance of AlarmTableViewCell.")
}
guard let alarm = self.alarms[safe: indexPath.row] else {
os_log("Could not unwrap alarm for indexPath in AlarmTableViewController.swift", log: OSLog.default, type: .default)
self.tableView.reloadData()
return AlarmTableViewCell()
}
let alarmNumber = alarm.value(forKey: "alarmNumber") as! Int
cell.alarmNumberLabel.text = "Alarm " + String(alarmNumber)
let beginTime = alarm.value(forKey: "startTimeInterval") as! Double
let endTime = alarm.value(forKey: "endTimeInterval") as! Double
let beginTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: beginTime)
let beginTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: beginTime)
cell.beginTimeLabel.text = formatTime(hour: beginTimeHour, minute: beginTimeMinute)
let endTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: endTime)
let endTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: endTime)
cell.endTimeLabel.text = formatTime(hour: endTimeHour, minute: endTimeMinute)
guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarm: alarm) else {
os_log("Could not get notificationUuids from AlarmMO in tableView(cellForRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return cell
}
os_log("----- notificationUuids: -----", log: OSLog.default, type: .debug)
for uuid in notificationUuids {
os_log("uuid: %#", log: OSLog.default, type: .debug, uuid)
}
return cell
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {
let alarmNotificationIdentifier = response.notification.request.identifier
var alarmActedOn: Alarm? = nil
for alarm in self.alarms {
guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarm: alarm) else {
os_log("Could not get notificationUuids from AlarmMO in tableView(forRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return
}
for notificationUuid in notificationUuids {
if notificationUuid == alarmNotificationIdentifier {
alarmActedOn = alarm
}
}
}
if response.actionIdentifier == "snooze" {
alarmActedOn?.setNewRandomAlarmTime()
}
completionHandler()
}
// MARK: Actions
#IBAction func unwindToAlarmList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? AddAlarmViewController, let alarm = sourceViewController.alarm {
let newIndexPath = IndexPath(row: self.alarms.count, section: 0)
os_log("There are %d notificationUuids attached to the alarm created", log: OSLog.default, type: .debug, alarm.notificationUuids.count)
saveAlarm(alarmToSave: alarm)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
// MARK: Private functions
#objc private func didReceiveNotification() {
os_log("entered the function", log: OSLog.default, type: .debug)
}
private func removeNotifications(notificationUuids: [String]) {
os_log("Removing %d alarm notifications", log: OSLog.default, type: .debug, notificationUuids.count)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.removePendingNotificationRequests(withIdentifiers: notificationUuids)
}
private func loadAlarms() {
os_log("loadAlarms() called", log: OSLog.default, type: .debug)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<Alarm>(entityName: "Alarm")
do {
if self.alarms.count == 0 {
self.alarms = try managedContext.fetch(fetchRequest)
os_log("Loading %d alarms", log: OSLog.default, type: .debug, self.alarms.count)
} else {
os_log("Didn't need to load alarms", log: OSLog.default, type: .debug)
}
} catch let error as NSError {
print("Could not fetch alarms. \(error), \(error.userInfo)")
}
}
private func saveAlarm(alarmToSave: Alarm) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Alarm", in: managedContext)!
let alarm = Alarm(entity: entity, insertInto: managedContext)
alarm.setValue(alarmToSave.alarmTime, forKeyPath: "alarmTime")
alarm.setValue(alarmToSave.alarmNumber, forKeyPath: "alarmNumber")
alarm.setValue(alarmToSave.startTimeInterval, forKeyPath: "startTimeInterval")
alarm.setValue(alarmToSave.endTimeInterval, forKeyPath: "endTimeInterval")
alarm.setValue(RecurrenceOptions.getRawValue(value: alarmToSave.recurrence), forKeyPath: "recurrenceIndex")
alarm.setValue(alarmToSave.notificationUuids, forKeyPath: "notificationUuids")
alarm.setValue(alarmToSave.note, forKeyPath: "note")
if managedContext.hasChanges {
do {
try managedContext.save()
self.alarms.append(alarm)
} catch let error as NSError {
print("Could not save alarm to CoreData. \(error), \(error.userInfo)")
}
} else {
os_log("No changes to the context to save", log: OSLog.default, type: .debug)
}
}
private func getNotificationUuidsFromAlarmMO(alarm: Alarm) -> [String]? {
guard let notificationUuids = alarm.value(forKey: "notificationUuids") as! [String]? else {
os_log("Found nil when attempting to unwrap notificationUuids in getNotificationUuidsFromAlarmMO() in AlarmTableViewController.swift, returning nil",
log: OSLog.default, type: .default)
return nil
}
return notificationUuids
}
private func requestUserNotificationsPermissionsIfNeeded() {
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.getNotificationSettings { (settings) in
guard settings.authorizationStatus == .authorized else {
return
}
os_log("User has notifications permissions enabled", log: OSLog.default, type: .debug)
}
notificationCenter.requestAuthorization(options: [.alert, .sound, .badge]) {
(granted, error) in
if !granted {
self.presentNotificationsPermissionDenialConfirmationAlert()
} else {
os_log("User enabled notifications permissions", log: OSLog.default, type: .debug)
}
}
return
}
private func presentNotificationsPermissionDenialConfirmationAlert() {
let alert = UIAlertController(title: "Are you sure you don't want to allow notifications?",
message: "The application cannot function without notifications.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "Yes", style: .cancel, handler: {
action in
fatalError("User declined to allow notifications permissions")
}))
alert.addAction(UIAlertAction(title: "No", style: .default, handler: {
action in
self.requestUserNotificationsPermissionsIfNeeded()
}))
self.present(alert, animated: true)
}
}
That is my AlarmTableViewController code. Here's my abridged Alarm.swift code:
class Alarm: NSManagedObject {
#NSManaged var alarmTime: Double
#NSManaged var alarmNumber: Int
#NSManaged var startTimeInterval: Double
#NSManaged var endTimeInterval: Double
#NSManaged var note: String
#NSManaged var notificationUuids: [String]
#NSManaged var recurrenceIndex: Int
let NUMBER_OF_ALLOWED_NOTIFICATIONS_CREATED_AT_ONE_TIME = 10
}
extension Alarm {
static func newAlarm(context: NSManagedObjectContext, alarmNumber: Int, timeIntervals: TimeIntervals, note: String, recurrence: RecurrenceOptions) -> Alarm? {
let startInterval = Alarm.convertToTimeDouble(hour: timeIntervals.hourStartInterval, minute: timeIntervals.minuteStartInterval)
let endInterval = Alarm.convertToTimeDouble(hour: timeIntervals.hourEndInterval, minute: timeIntervals.minuteEndInterval)
if endInterval < startInterval {
os_log("Error: Alarm time endInterval is before startInterval", log: OSLog.default, type: .info)
return nil
}
let newAlarm = Alarm(context: context)
newAlarm.alarmNumber = alarmNumber
newAlarm.note = note
newAlarm.recurrenceIndex = RecurrenceOptions.getRawValue(value: recurrence)
newAlarm.notificationUuids = [String]()
newAlarm.startTimeInterval = startInterval
newAlarm.endTimeInterval = endInterval
newAlarm.setNewRandomAlarmTime()
return newAlarm
}
var recurrence: RecurrenceOptions {
get {
return RecurrenceOptions.getValueFromIndex(index: recurrenceIndex)
}
set {
self.recurrenceIndex = RecurrenceOptions.getRawValue(value: newValue)
}
}
var hour: Int {
return Int(floor(self.alarmTime))
}
var minute: Int {
return Int(round((self.alarmTime - floor(self.alarmTime)) * 60))
}
func setNewRandomAlarmTime() {
let startInterval = self.startTimeInterval
let endInterval = self.endTimeInterval
let currentDateAndTime = Date()
let currentDateAndTimeComponents = Calendar.current.dateComponents([.hour, .minute], from: currentDateAndTime)
guard let currentHour = currentDateAndTimeComponents.hour else {
os_log("Could not unwrap currentDateAndTimeComponents.hour in Alarm.setNewRandomAlarmTime()", log: OSLog.default, type: .default)
return
}
guard let currentMinute = currentDateAndTimeComponents.minute else {
os_log("Could not unwrap currentDateAndTimeComponents.minute in Alarm.setNewRandomAlarmTime()", log: OSLog.default, type: .default)
return
}
let currentTimeDouble = Alarm.convertToTimeDouble(hour: currentHour, minute: currentMinute)
// We should start the random alarm interval from the current
// time if the current time is past the startInterval already
if currentTimeDouble > startInterval {
self.alarmTime = Double.random(in: currentTimeDouble ... endInterval)
} else {
self.alarmTime = Double.random(in: startInterval ... endInterval)
}
scheduleNotifications()
}
func scheduleNotifications() {
os_log("Attempting to create alarm with time %d:%02d", log: OSLog.default, type: .info, self.hour, self.minute)
let date = Date()
let calendar = Calendar.current
let currentDateComponents = calendar.dateComponents([.year, .month, .day, .timeZone, .hour, .minute], from: date)
createNotifications(dateComponents: currentDateComponents)
}
//MARK: Private functions
private func createNotifications(dateComponents: DateComponents) {
switch (self.recurrence) {
case .today:
self.createNotification(for: dateComponents)
case .tomorrow:
self.createNotification(for: self.day(after: dateComponents))
case .daily:
var numberOfCreatedNotifications = 0
var currentDay: DateComponents? = dateComponents
while numberOfCreatedNotifications < self.NUMBER_OF_ALLOWED_NOTIFICATIONS_CREATED_AT_ONE_TIME {
self.createNotification(for: currentDay)
currentDay = self.day(after: currentDay)
numberOfCreatedNotifications += 1
}
}
}
private func createNotification(for dateComponents: DateComponents?) {
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "Random Alarm"
content.subtitle = "It's time!"
content.body = self.note
content.sound = UNNotificationSound.default
guard let dateComponents = dateComponents else {
os_log("Could not unwrap dateComponents in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
return
}
let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
self.notificationUuids.append(uuidString)
guard let day = dateComponents.day else {
os_log("Could not unwrap dateComponents.day in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
return
}
guard let hour = dateComponents.hour else {
os_log("Could not unwrap dateComponents.hour in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
return
}
guard let minute = dateComponents.minute else {
os_log("Could not unwrap dateComponents.minute in createNotification() in Alarm.swift", log: OSLog.default, type: .debug)
return
}
os_log("Creating notification for day: %d, time: %d:%02d, with uuid=%s", log: OSLog.default, type: .debug, day, hour, minute, uuidString)
center.add(request) { (error) in
if let err = error {
print("error \(err.localizedDescription)")
}
}
}
private func day(after dateComponents: DateComponents?) -> DateComponents? {
let calendar = Calendar.autoupdatingCurrent
guard let dateComponents = dateComponents,
let date = calendar.date(from: dateComponents),
let tomorrow = calendar.date(byAdding: .day, value: 1, to: date)
else {
os_log("Could not calculate tomorrow in Alarm.swift", log: OSLog.default, type: .debug)
return nil
}
let newDateComponents = calendar.dateComponents([.year, .month, .day, .timeZone, .hour, .minute], from: tomorrow)
return newDateComponents
}
}
I figured it out. In createNotification() in Alarm.swift I was creating the notification for the current time, not the random alarm time. Simple mistake, but that meant that the alarm time ended up before the interval, and once you reopen the app it deletes the alarm because it's "old".
Related
I have a bug in my application that I just found, in this method:
private func createNotifications(dateComponents: DateComponents) {
switch (recurrence) {
case .today:
createNotification(for: dateComponents)
case .tomorrow:
createNotification(for: day(after: dateComponents))
case .daily:
let center = UNUserNotificationCenter.current()
center.getPendingNotificationRequests { (notifications) in
var numberOfCreatableNotifications = 64 - notifications.count
var numberOfCreatedNotifications = 0
var currentDay: DateComponents? = dateComponents
while numberOfCreatableNotifications > 0
&& numberOfCreatedNotifications < self.NUMBER_OF_ALLOWED_NOTIFICATIONS_CREATED_AT_ONE_TIME {
self.createNotification(for: currentDay)
currentDay = self.day(after: currentDay)
numberOfCreatableNotifications -= 1
numberOfCreatedNotifications += 1
}
}
}
}
I'm trying to create an alarm with a certain recurrence option (today, tomorrow, or daily) and the daily case wasn't working. The code above is in Alarm.swift. When an alarm is created it's passed back to the main view controller for saving in Core Data. I realized that the notifications aren't being saved in Core Data because they aren't created by the time the alarm is saved in Core Data. There's this closure center.getPendingNotificationRequests() that apparently takes some time before it returns and runs the completion handler.
I'm using the getPendingNotificationRequests() method because I'm trying to determine how many notifications I have left that I can create (max of 64).
Here's my question: Should I continue to use this async method and just do some saving in Core Data during the completion handler? Or should the code for saving stay in the main view controller and I just stop using this async method, because I don't need it for some reason?
Thanks
BTW, Here's most of the code from my AlarmTableViewController file, the main VC in my application:
//MARK: Public properties
var alarms = [AlarmMO]()
let ALARM_CELL_IDENTIFIER = "AlarmTableViewCell"
override func viewDidLoad() {
super.viewDidLoad()
requestUserNotificationsPermissionsIfNeeded()
NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil)
loadAlarms()
for alarm in self.alarms {
os_log("There are %d notifications for alarm %d", log: OSLog.default, type: .debug, alarm.notificationUuids.count, alarm.alarmNumber)
}
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil)
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.alarms.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: ALARM_CELL_IDENTIFIER, for: indexPath) as? AlarmTableViewCell else {
fatalError("The dequeued cell is not an instance of AlarmTableViewCell.")
}
guard let alarmMO = self.alarms[safe: indexPath.row] else {
os_log("Could not unwrap alarm for indexPath in AlarmTableViewController.swift", log: OSLog.default, type: .default)
self.tableView.reloadData()
return AlarmTableViewCell()
}
let alarmNumber = alarmMO.value(forKey: "alarmNumber") as! Int
let beginTime = alarmMO.value(forKey: "startTimeInterval") as! Double
let endTime = alarmMO.value(forKey: "endTimeInterval") as! Double
cell.alarmNumberLabel.text = "Alarm " + String(alarmNumber)
let beginTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: beginTime)
let beginTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: beginTime)
cell.beginTimeLabel.text = formatTime(hour: beginTimeHour, minute: beginTimeMinute)
let endTimeHour = Alarm.extractHourFromTimeDouble(alarmTimeDouble: endTime)
let endTimeMinute = Alarm.extractMinuteFromTimeDouble(alarmTimeDouble: endTime)
cell.endTimeLabel.text = formatTime(hour: endTimeHour, minute: endTimeMinute)
guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarmMO) else {
os_log("Could not get notificationUuids from AlarmMO in tableView(cellForRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return cell
}
os_log("----- notificationUuids: -----", log: OSLog.default, type: .debug)
for uuid in notificationUuids {
os_log("uuid: %#", log: OSLog.default, type: .debug, uuid)
}
return cell
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
guard let alarm = self.alarms[safe: indexPath.row] else {
os_log("Could not get alarm from its indexPath in AlarmTableViewController.swift", log: OSLog.default, type: .default)
self.tableView.reloadData()
return
}
guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarm) else {
os_log("Could not get notificationUuids from AlarmMO in tableView(forRowAt:) in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return
}
self.removeNotifications(notificationUuids: notificationUuids)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
managedContext.delete(alarm)
self.alarms.remove(at: indexPath.row)
for (index, alarm) in self.alarms.enumerated() {
let alarmNumber = index + 1
alarm.setValue(alarmNumber, forKey: "alarmNumber")
}
self.saveContext()
self.tableView.reloadData()
}
}
// MARK: Actions
#IBAction func unwindToAlarmList(sender: UIStoryboardSegue) {
if let sourceViewController = sender.source as? AddAlarmViewController, let alarm = sourceViewController.alarm {
let newIndexPath = IndexPath(row: self.alarms.count, section: 0)
os_log("There are %d notificationUuids attached to the alarm created", log: OSLog.default, type: .debug, alarm.notificationUuids.count)
saveAlarm(alarmToSave: alarm)
tableView.insertRows(at: [newIndexPath], with: .automatic)
}
}
// MARK: Private functions
#objc private func didBecomeActive() {
deleteOldAlarms {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
private func deleteOldAlarms(completionHandler: #escaping () -> Void) {
os_log("deleteOldAlarms() called", log: OSLog.default, type: .default)
let notificationCenter = UNUserNotificationCenter.current()
var alarmsToDelete = [AlarmMO]()
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
notificationCenter.getPendingNotificationRequests(completionHandler: { (requests) in
alarmsToDelete = self.calculateAlarmsToDelete(requests: requests)
os_log("Deleting %d alarms", log: OSLog.default, type: .debug, alarmsToDelete.count)
for alarmMOToDelete in alarmsToDelete {
guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarmMOToDelete) else {
os_log("Could not get notificationUuids from AlarmMO in deleteOldAlarms() in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return
}
self.removeNotifications(notificationUuids: notificationUuids)
managedContext.delete(alarmMOToDelete)
self.alarms.removeAll { (alarmMO) -> Bool in
return alarmMOToDelete == alarmMO
}
}
completionHandler()
})
}
private func calculateAlarmsToDelete(requests: [UNNotificationRequest]) -> [AlarmMO] {
var activeNotificationUuids = [String]()
var alarmsToDelete = [AlarmMO]()
for request in requests {
activeNotificationUuids.append(request.identifier)
}
for alarm in self.alarms {
guard let notificationUuids = self.getNotificationUuidsFromAlarmMO(alarmMO: alarm) else {
os_log("Could not get notificationUuids from AlarmMO in deleteOldAlarms() in AlarmTableViewController.swift", log: OSLog.default, type: .debug)
return []
}
let activeNotificationUuidsSet: Set<String> = Set(activeNotificationUuids)
let alarmUuidsSet: Set<String> = Set(notificationUuids)
let union = activeNotificationUuidsSet.intersection(alarmUuidsSet)
if union.isEmpty {
alarmsToDelete.append(alarm)
}
}
return alarmsToDelete
}
private func removeNotifications(notificationUuids: [String]) {
os_log("Removing %d alarm notifications", log: OSLog.default, type: .debug, notificationUuids.count)
let notificationCenter = UNUserNotificationCenter.current()
notificationCenter.removePendingNotificationRequests(withIdentifiers: notificationUuids)
}
private func loadAlarms() {
os_log("loadAlarms() called", log: OSLog.default, type: .debug)
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let fetchRequest = NSFetchRequest<AlarmMO>(entityName: "Alarm")
do {
if self.alarms.count == 0 {
self.alarms = try managedContext.fetch(fetchRequest)
os_log("Loading %d alarms", log: OSLog.default, type: .debug, self.alarms.count)
} else {
os_log("Didn't need to load alarms", log: OSLog.default, type: .debug)
}
} catch let error as NSError {
print("Could not fetch alarms. \(error), \(error.userInfo)")
}
}
private func saveAlarm(alarmToSave: Alarm) {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let managedContext = appDelegate.persistentContainer.viewContext
let entity = NSEntityDescription.entity(forEntityName: "Alarm", in: managedContext)!
let alarmMO = AlarmMO(entity: entity, insertInto: managedContext)
alarmMO.setValue(alarmToSave.alarmTime, forKeyPath: "alarmTime")
alarmMO.setValue(alarmToSave.alarmNumber, forKeyPath: "alarmNumber")
alarmMO.setValue(alarmToSave.alarmIntervalBeginTimeDouble, forKeyPath: "startTimeInterval")
alarmMO.setValue(alarmToSave.alarmIntervalEndTimeDouble, forKeyPath: "endTimeInterval")
alarmMO.setValue(alarmToSave.recurrence.hashValue, forKeyPath: "recurrence")
alarmMO.setValue(alarmToSave.notificationUuids, forKeyPath: "notificationUuids")
if managedContext.hasChanges {
do {
try managedContext.save()
self.alarms.append(alarmMO)
} catch let error as NSError {
print("Could not save alarm to CoreData. \(error), \(error.userInfo)")
}
} else {
os_log("No changes to the context to save", log: OSLog.default, type: .debug)
}
}
private func getNotificationUuidsFromAlarmMO(alarmMO: AlarmMO) -> [String]? {
guard let notificationUuids = alarmMO.value(forKey: "notificationUuids") as! [String]? else {
os_log("Found nil when attempting to unwrap notificationUuids in getNotificationUuidsFromAlarmMO() in AlarmTableViewController.swift, returning nil",
log: OSLog.default, type: .default)
return nil
}
return notificationUuids
}
It would probably be simplest if this method always runs asynchronously and always calls a completion handler. So, your calling code would hand this method a completion handler as it calls it, and stop. Later this method would call the caller back on the completion handler, thus notifying it that we created some notifications. A possible sketch:
private func createNotifications(dateComponents: DateComponents, completion: (Int) -> Void) {
let center = UNUserNotificationCenter.current()
center.getPendingNotificationRequests { (notifications) in
switch (recurrence) {
// this is wrong, you should not be looking outside this method for the recurrence value...
// ... but it's just a sketch
case .today:
createNotification(for: dateComponents)
completion(1)
case .tomorrow:
createNotification(for: day(after: dateComponents))
completion(1)
case .daily:
// ... decide whether to create the notifications or not ...
// ... and create however many you decide to ...
let howManyWeCreated = // however many we created
completion(howManyWeCreated)
}
}
}
I'm trying to copy specific events from all calendars to the target calendar. Unfortunately my events are not saving in target calendar.
Simply my code in steps:
Check permissions (success)
Load calendars (success)
Load events (success)
Save events (failed)
I'm sure there are events to save from terminal which prints "Trying to save" couple of times.
And it looks like code pass through "try self.eventStore.save(event, span: .thisEvent)" and exits function there without calling "Saved" or entering catch clause.
There is a source code:
import UIKit
import EventKit
class ViewController: UIViewController{
#IBOutlet weak var status: UILabel!
var calendars: [EKCalendar]?
var targetCalendar: EKCalendar?
var targetCalendarEvents: [EKEvent]?
let eventStore = EKEventStore()
let targetCalendarName = "TargetCalendarName"
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillAppear(_ animated: Bool) {
DispatchQueue.main.async{
self.status.text = "Idle"
}
checkCalendarAuthorizationStatus()
}
func checkCalendarAuthorizationStatus() {
let status = EKEventStore.authorizationStatus(for: EKEntityType.event)
switch (status) {
case EKAuthorizationStatus.notDetermined:
// This happens on first-run
requestAccessToCalendar()
case EKAuthorizationStatus.authorized:
// Things are in line with being able to show the calendars in the table view
loadCalendars()
loadEvents()
case EKAuthorizationStatus.restricted, EKAuthorizationStatus.denied:
// We need to help them give us permission
print("Missing permissions [00]")
}
}
func requestAccessToCalendar() {
eventStore.requestAccess(to: EKEntityType.event, completion: {
(accessGranted: Bool, error: Error?) in
if accessGranted == true {
DispatchQueue.main.async(execute: {
self.loadCalendars()
self.loadEvents()
})
} else {
print("Missing permissions [01]")
}
})
}
func loadEvents(){
print("Loading..")
DispatchQueue.main.async{
self.status.text = "Loading"
}
let eventStore = EKEventStore()
var initialized = false
//Two months
let dateTo = NSDate(timeIntervalSinceNow: +30*24*3600 * 2)
for calendar in self.calendars! {
let predicate = eventStore.predicateForEvents(withStart: Date() as Date as Date, end: dateTo as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
if calendar.title == targetCalendarName {
print("Initialized")
targetCalendar = calendar
targetCalendarEvents = events
initialized = true
}
}
if(!initialized){
print("Not Initialized")
} else {
for calendar in self.calendars! {
let predicate = eventStore.predicateForEvents(withStart: Date() as Date as Date, end: dateTo as Date, calendars: [calendar])
let events = eventStore.events(matching: predicate)
if calendar.title != targetCalendarName && calendar.title != "Contacts" {
//print("Loaded Calendar \(calendar.title)")
print("Loaded Calendar")
for event in events {
if(!(event.location?.isEmpty)!){
//print("Event \(event.location ?? "Missing Location") \(event.startDate) \(event.endDate)")
addEventToTarget(eventToAdd: event)
}
}
}
}
}
DispatchQueue.main.async {
self.status.text = "Done"
}
print("Done")
}
func loadCalendars() {
self.calendars = eventStore.calendars(for: EKEntityType.event)
}
func addEventToTarget(eventToAdd: EKEvent){
eventStore.requestAccess(to: .event) { (granted, error) in
for event in self.targetCalendarEvents! {
if(!(event.location?.isEmpty)!){
if(
eventToAdd.title == event.title &&
eventToAdd.startDate == event.startDate &&
eventToAdd.endDate == event.endDate
){
print("Possible duplicate - skipping")
return
}
}
}
if (granted) && (error == nil) {
let event:EKEvent = EKEvent(eventStore: self.eventStore)
event.title = eventToAdd.title
event.startDate = eventToAdd.startDate
event.endDate = eventToAdd.endDate
event.notes = ""
event.location = eventToAdd.location
event.calendar = self.targetCalendar
//print("Trying to save \(event.title) \(String(describing: event.location))")
print("Trying to save")
do {
try self.eventStore.save(event, span: .thisEvent)
print("Saved \(event.title) \(String(describing: event.location)) in \(event.calendar.title)")
} catch {
print("failed to save event with error : \(error as NSError)")
}
}
else{
print("failed to save event with error : \(String(describing: error)) or access not granted")
}
}
}
}
TargetCalendarName is correct calendar name
Sometimes there are events which saves successfully but only couple of them (2-5) for hundreds which should save. I don't have a clue why.
So 5 for 200 is not enough for me.
Thanks to #matt for proposing a solution.
Now I'm saving events ONCE (previously I was requesting access for every event which is very bad), so I request access to eventStore once and can save events succesfully.
func saveEvents(){
eventStore.requestAccess(to: .event) { (granted, error) in
if (granted) && (error == nil) {
for event in self.eventsToCopy {
print("Trying to save")
do {
try self.eventStore.save(event, span: .thisEvent)
print("Saved \(event.title) \(String(describing: event.location)) in \(event.calendar.title)")
} catch {
print("failed to save event with error : \(error as NSError)")
}
}
}
else{
print("failed to save event with error : \(String(describing: error)) or access not granted")
}
}
}
I'm trying to retrieve the steps data from Health Kit and get an update everytime this data changes.
But when the HKHealthStore calls its execute method on the query, the app crash:
And I have no idea why. Any help is welcome.
Here is my code:
func sretrieveRealTimeSteps(completion: #escaping (Int) -> Void) {
let calendar = Calendar.current
var interval = DateComponents()
interval.day = 1
var anchorComponents = calendar.dateComponents([.day, .month, .year, .weekday], from: Date())
anchorComponents.hour = 0
guard let anchorDate = calendar.date(from: anchorComponents) else { completion(0); return }
guard let quantityType = HKObjectType.quantityType(forIdentifier: .stepCount) else { completion(0); return }
let realTimeQuery = HKStatisticsCollectionQuery(quantityType: quantityType,
quantitySamplePredicate: nil,
options: .cumulativeSum,
anchorDate: anchorDate,
intervalComponents: interval)
realTimeQuery.statisticsUpdateHandler = { query, statistics, results, error in
guard let statsCollection = results else {
completion(0)
return
}
statsCollection.enumerateStatistics(from: Date(), to: Date()) { statistics, stop in
if let quantity = statistics.sumQuantity() {
let value = quantity.doubleValue(for: HKUnit.count())
completion(Int(value))
} else {
completion(0)
}
}
}
healthKitStore.execute(realTimeQuery)
}
Also, when I used a one time query there is no problem:
query.initialResultsHandler = { query, results, error in
guard let statsCollection = results else {
NotificationView.showHealthKitRetrieveInfoFailedNotification()
completion(nil, error)
return
}
var values = [Int]()
let endDate = Date()
let startDate = calendar.date(byAdding: .day, value: -6, to: endDate) ?? Date()
statsCollection.enumerateStatistics(from: startDate, to: endDate) { statistics, stop in
if let quantity = statistics.sumQuantity() {
let date = statistics.startDate
let value = quantity.doubleValue(for: HKUnit.count())
values.append(Int(value))
if date.isSameDayThan(endDate, timeZone: TimeZone.current) {
completion(values, nil)
}
} else {
values.append(0)
if values.count == 7 {
stop.pointee = true
completion(values, nil)
}
}
}
}
EDIT: Stacktrace
I am new to swift and i am practicing "Adding a Calendar Event" from this website: http://www.ioscreator.com/tutorials/add-event-calendar-tutorial-ios8-swift, but I am getting the following errors at line
let result = store.saveEvent(event, span: .ThisEvent, commit: error)
Call can throw, but it is not marked with 'try' and the error is not handled
Cannot convert value of type 'NSError?' to expected argument type 'Bool'
Code:
import UIKit
import EventKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(EKEntityType.Event) {
case .Authorized:
insertEvent(eventStore)
case .Denied:
print("Access denied")
case .NotDetermined:
// 3
eventStore.requestAccessToEntityType(EKEntityType.Event, completion:
{[weak self] (granted: Bool, error: NSError?) -> Void in
if granted {
self!.insertEvent(eventStore)
} else {
print("Access denied")
}
})
default:
print("Case Default")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func insertEvent(store : EKEventStore){
let calendars = store.calendarsForEntityType(EKEntityType.Event) as! [EKCalendar]
for calendar in calendars{
if calendar.title == "ioscreater"{
let startDate = NSDate()
let endDate = startDate.dateByAddingTimeInterval(2*60*60)
//Create event
var event = EKEvent(eventStore : store)
event.calendar = calendar
event.title = "New Meeting"
event.startDate = startDate
event.endDate = endDate
//Save event in Calendar
var error : NSError?
let result = store.saveEvent(event, span: .ThisEvent, commit: error)
if result == false{
if let theError = error{
print ("An error occured \(theError)")
}
}
}
}
}
}
I googled the errors but I could not find an appropriate solution.
This works for me in Swift 3.0
Firstly, You need to add "Privacy - Calendars Usage Description" in info.plist.
import EventKit
func addEventToCalendar(title: String, description: String?, startDate: Date, endDate: Date, completion: ((_ success: Bool, _ error: NSError?) -> Void)? = nil) {
let eventStore = EKEventStore()
eventStore.requestAccess(to: .event, completion: { (granted, error) in
if (granted) && (error == nil) {
let event = EKEvent(eventStore: eventStore)
event.title = title
event.startDate = startDate
event.endDate = endDate
event.notes = description
event.calendar = eventStore.defaultCalendarForNewEvents
do {
try eventStore.save(event, span: .thisEvent)
} catch let e as NSError {
completion?(false, e)
return
}
completion?(true, nil)
} else {
completion?(false, error as NSError?)
}
})
}
Then, you can easily call this method like the following:
addEventToCalendar(title: "Best friend birthday", description: "Remember you or miss you!", startDate: NSDate(), endDate: NSDate())
Thanks and enjoy coding!!!
func insertEvent(store : EKEventStore){
let calendars = store.calendarsForEntityType(EKEntityType.Event)
for calendar in calendars{
if calendar.title == "ioscreater"{
let startDate = NSDate()
let endDate = startDate.dateByAddingTimeInterval(2*60*60)
//Create event
var event = EKEvent(eventStore : store)
event.calendar = calendar
event.title = "New Meeting"
event.startDate = startDate
event.endDate = endDate
//Save event in Calendar
do {
try store.saveEvent(event, span: .ThisEvent)
} catch {
// Do error stuff here
}
}
Add saveEvent code in try,catch like below :
do {
try store.saveEvent(event, span: .ThisEvent)
} catch let err as NSError{
print ("An error occured \(err.description)")
}
I'm working on this subscription system for my app,it took a few days.But as I'm working more on it,it makes more problems.
Sometimes it happens that the app crashes when clicking on buy subscription.
Also I'm having the problem of not knowing when to stop the Please wait.... alert.I need to place the code in:
case .Purchasing:
self.alert_show()
break
But I don't know where to end it,I need to know the information when is the alert from iTunes loaded than to stop the Please wait.... alert.
The biggest problem right now that I'm facing is the crashing sometimes when i click on the Buy Button.
The crashing happens when first time launching the app,and clicking one the Buy Subscription.
Here is the code for the Buy Subscription:
#IBAction func buy_sub(sender: AnyObject) {
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
Here is the error that I'm getting.
fatal error: unexpectedly found nil while unwrapping an Optional value..
I would like to share with you my source code of the subscription.If you have any advices what to add,or to correct or to correct this problems that i have it would be great.
Here is the full source code of the Subscription View:
class SubscriptionViewController: UIViewController ,SKPaymentTransactionObserver, SKProductsRequestDelegate {
//var productID = ""
var product: SKProduct!
#IBOutlet var buy_trial_button: UIButton!
override func viewDidAppear(animated: Bool) {
if(SKPaymentQueue.canMakePayments()) {
} else {
buy_trial_button.enabled = false
message_alert("Please enable IAPS to continue(Credit card information required in your iTunes account).")
}
//validateReceipt()
let keystore = NSUbiquitousKeyValueStore.defaultStore()
if keystore.objectForKey("expiration_date") != nil{
let expiration: AnyObject? = keystore.objectForKey("expiration_date")
let today = NSDate()
let expiredate = expiration as! NSDate
print("expiredate is %#",expiredate,today)
// var date1 = NSDate()
// var date2 = NSDate()
if(today.compare(expiredate) == NSComparisonResult.OrderedDescending){
print("today is later than expiredate")
validateReceipt()
print("Validating")
}else if(today.compare(expiredate) == NSComparisonResult.OrderedAscending){
print("today is earlier than expiredate")
self.performSegueWithIdentifier("subscriptionPassed", sender: self)
}
}else{
print("First time launch")
}
}
override func viewDidLoad() {
super.viewDidLoad()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
self.getProductInfo()
//SKPaymentQueue.defaultQueue().addTransactionObserver(self)
//self.getProductInfo()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func viewWillDisappear(animated: Bool) {
}
#IBAction func private_policy(sender: AnyObject) {
let openLink = NSURL(string : "http://arsutech.com/private_policy.php")
UIApplication.sharedApplication().openURL(openLink!)
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if(segue.identifier == "subscriptionPassed")
{
}
}
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse){
let products = response.products
if products.count != 0
{
product = products[0] as SKProduct
print(product.localizedTitle + "\n" + product.localizedDescription)
}
}
func getProductInfo(){
if (SKPaymentQueue.canMakePayments()){
let productID:NSSet = NSSet(object: "com.sxxxxxxxxxxxxxxxxx")
let request:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>)
request.delegate = self
request.start()
}
}
#IBAction func buy_sub(sender: AnyObject) {
let payment:SKPayment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
#IBAction func buy_pro(sender: AnyObject) {
let openLink = NSURL(string : "https://itunes.apple.com/us/app/home-workouts-exercises-mma/id1060747118?mt=8")
UIApplication.sharedApplication().openURL(openLink!)
}
#IBAction func exit(sender: AnyObject) {
exit(0)
}
func validateReceipt(){
alert_show()
let mainBundle = NSBundle.mainBundle() as NSBundle;
let receiptUrl = mainBundle.appStoreReceiptURL;
let isPresent = receiptUrl?.checkResourceIsReachableAndReturnError(NSErrorPointer());
if(isPresent == true){
let data = NSData(contentsOfURL: receiptUrl! );
// Create the JSON object that describes the request
let requestContents = NSMutableDictionary();
//let encodeddata = data!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions());
let encodeddata = data!.base64EncodedString();
//print("encodeddata = \(encodeddata)");
requestContents.setObject(encodeddata, forKey: "receipt-data");
requestContents.setObject("c40f23af1aa44e159aezzzzzzzzzzzzzz", forKey: "password");
var requestData : NSData?
do{
requestData = try NSJSONSerialization.dataWithJSONObject(requestContents, options: NSJSONWritingOptions());
}catch{
// NSLog("Error in json data creation at verifyPaymentReceipt");
}
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as NSString
let file = "\(documentsPath)/requestData"
if(NSFileManager.defaultManager().createFileAtPath(file, contents: data, attributes: nil)){
NSLog("File %# ",file);
}
else{
//NSLog("error File %# ",file);
}
if(requestData != nil){
//let strRequestData = NSString(data: requestData!, encoding: NSUTF8StringEncoding);
//print(" strRequestData = \(strRequestData)");
// Create a POST request with the receipt data.
//https://buy.itunes.apple.com/verifyReceipt
//https://sandbox.itunes.apple.com/verifyReceipt
let storeURL = NSURL(string: "https://sandbox.itunes.apple.com/verifyReceipt");
let storeRequest = NSMutableURLRequest(URL: storeURL!);
storeRequest.HTTPMethod = "POST";
storeRequest.HTTPBody = requestData;
// Make a connection to the iTunes Store on a background queue.
let queue = NSOperationQueue();
NSURLConnection.sendAsynchronousRequest(storeRequest, queue: queue, completionHandler: { (response : NSURLResponse?, data : NSData?, error : NSError?) -> Void in
if(error != nil){
//Handle Error
}
else{
let d = NSString(data: data!, encoding: NSUTF8StringEncoding);
// NSLog("DATA:%#", d!);
let dataA = d!.dataUsingEncoding(NSUTF8StringEncoding)
var jsonResponseInternal: NSMutableDictionary?
do{
jsonResponseInternal = try NSJSONSerialization.JSONObjectWithData(dataA!,options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
//print(jsonResponseInternal);
}catch{
// NSLog("Parsing issue : verifyPaymentReceipt");
}
var jsonResponse: NSMutableDictionary?
do{
jsonResponse = try NSJSONSerialization.JSONObjectWithData(data!,
options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
//print(jsonResponse);
}catch{
// NSLog("Parsing issue : verifyPaymentReceipt");
}
if(jsonResponse != nil){
if(jsonResponse != nil){
//NSLog("Expiration Date: %#", jsonResponse!);
//print("Passed")
/*
NSUserDefaults.standardUserDefaults().setBool(true, forKey: "Purchase")
NSUserDefaults.standardUserDefaults().synchronize()
*/
//self.performSegueWithIdentifier("subscriptionPassed", sender: self)
if jsonResponse!["status"] as? Int == 0 {
//print("Sucessfully returned internal receipt data")
if let receiptInfo: NSArray = jsonResponse!["latest_receipt_info"] as? NSArray {
let lastReceipt = receiptInfo.lastObject as! NSDictionary
var trial_period: Bool = false
// Get last receipt
//print("LAST RECEIPT INFORMATION \n",lastReceipt)
print("Last from internal memory",lastReceipt["original_transaction_id"])
//var is_trial = lastReceipt["is_trial_period"] as! Bool
//var date_bought =
//var date_expires =
// Format date
//print(is_trial)
// Format date
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss VV"
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
// Get Expiry date as NSDate
let subscriptionExpirationDate: NSDate = formatter.dateFromString(lastReceipt["expires_date"] as! String) as NSDate!
print("\n - DATE SUBSCRIPTION EXPIRES = \(subscriptionExpirationDate)")
let currentDateTime = NSDate()
print(currentDateTime)
if var is_trial:Bool = false{
if(lastReceipt["is_trial_period"] as? String == "Optional(\"true\")"){
is_trial = true
trial_period = is_trial
}else if(lastReceipt["is_trial_period"] as? String == "Optional(\"false\")"){
is_trial = false
trial_period = is_trial
}
}
if (subscriptionExpirationDate.compare(currentDateTime) == NSComparisonResult.OrderedDescending) {
self.alrt_close()
print("Pass");
print("The trial period is \(trial_period)")
let keystore = NSUbiquitousKeyValueStore.defaultStore()
keystore.setObject(subscriptionExpirationDate,forKey:"expiration_date")
keystore.synchronize()
self.performSegueWithIdentifier("subscriptionPassed", sender: self)
} else if (subscriptionExpirationDate.compare(currentDateTime) == NSComparisonResult.OrderedAscending) {
print("Not Pass");
print("Subscription expired")
self.alrt_close()
//self.message_alert("Subscription expired")
}
}
}
}
}
}
});
}
}
}
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
for transaction:AnyObject in transactions {
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchasing:
//self.alrt_close()
break
case .Deferred:
self.alert_show()
break
case .Purchased:
alrt_close()
self.validateReceipt()
//self.setExpirationDate()
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
//dismissViewControllerAnimated(false, completion: nil)
break
case .Failed:
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
print("Not called Expired")
message_alert("Error while purchasing!")
break
case .Restored:
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
print("Restored")
break
default:
break
}
}
}
}
//Alrt
func message_alert(let message_A:String){
let alert = UIAlertController(title: "", message: "\(message_A)", preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
func alert_show(){
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .Alert)
alert.view.tintColor = UIColor.blackColor()
let loadingIndicator: UIActivityIndicatorView = UIActivityIndicatorView(frame: CGRectMake(10, 5, 50, 50)) as UIActivityIndicatorView
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.Gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
presentViewController(alert, animated: true, completion: nil)
}
func alrt_close(){
self.dismissViewControllerAnimated(false, completion: nil)
}
}