Stop notifications at specific time using UIDatePicker - ios

I managed to setup notifications using a UIDatePicker but my problem is that I want to make them stop by using an other UIDatePicker (datePickerStop).
So basically it Schedules to start and end notifications with two different UIDatePickers inside a Switch object and an if statement.
This is code that I have but it doesn't seem to work or I don't place it in the right place.
Any ideas?
let notificationArray = UIApplication.sharedApplication().scheduledLocalNotifications!
for notification in notificationArray {
if notification.fireDate == fixedNotificationDate(datePickerStop.date) {
UIApplication.sharedApplication().cancelLocalNotification(notification)
}
}
This is the complete code
override func viewDidLoad() {
super.viewDidLoad()
updateUI()
let defaults = NSUserDefaults.standardUserDefaults()
if (defaults.objectForKey("SwitchState") != nil) {
SwitchOutlet.on = defaults.boolForKey("SwitchState")
}
if (SwitchOutlet.on) {
Label1Outlet.text = "Notifications are On"
} else {
Label1Outlet.text = "Notifications are Off"
}
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func Switch(sender: AnyObject) {
let defaults = NSUserDefaults.standardUserDefaults()
if (SwitchOutlet.on) {
Label1Outlet.text = "Notifications are On"
defaults.setBool(true, forKey: "SwitchState")
let notification = UILocalNotification()
notification.fireDate = fixedNotificationDate(datePicker.date)
notification.repeatInterval = NSCalendarUnit.Minute
notification.alertBody = “Message”
notification.alertAction = “Lock!”
notification.soundName = "NotifSound.caf"
notification.userInfo = ["CustomField1": "w00t"]
guard let settings = UIApplication.sharedApplication().currentUserNotificationSettings() else { return }
if settings.types == .None {
let ac = UIAlertController(title: "Can't Activate", message: "Either we don't have permission to schedule notifications, or we haven't asked yet.", preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(ac, animated: true, completion: nil)
return
}
UIApplication.sharedApplication().scheduleLocalNotification(notification)
} else {
Label1Outlet.text = "Notifications are Off"
defaults.setBool(false, forKey: "SwitchState")
UIApplication.sharedApplication().cancelAllLocalNotifications()
}
}
func fixedNotificationDate(dateToFix: NSDate) -> NSDate {
let dateComponents: NSDateComponents = NSCalendar.currentCalendar().components([NSCalendarUnit.Day, NSCalendarUnit.Month, NSCalendarUnit.Year, NSCalendarUnit.Hour, NSCalendarUnit.Minute], fromDate: dateToFix)
dateComponents.second = 0
let fixedDate: NSDate = NSCalendar.currentCalendar().dateFromComponents(dateComponents)!
return fixedDate
}
func updateUI() {
let currentSettings = UIApplication.sharedApplication().currentUserNotificationSettings()
if currentSettings?.types != nil {
if currentSettings!.types == [UIUserNotificationType.Badge, UIUserNotificationType.Alert] {
}
else if currentSettings!.types == [UIUserNotificationType.Badge] {
}
else if currentSettings!.types == UIUserNotificationType.None {
}
}
}
}

You can add userInfo to your notification when scheduled
let notification:UILocalNotification = UILocalNotification()
notification.userInfo = ["day": day, "hour": hour]
UIApplication.sharedApplication().scheduleLocalNotification(notification)
And when you are trying to cancel just find it with the userInfo
let app = UIApplication.sharedApplication()
let day: Int = 1
let hour: Int = 2
for singleNotification in app.scheduledLocalNotifications!{
let notDay = singleNotification.userInfo!["day"] as! Int
let notHour = singleNotification.userInfo!["hour"] as! Int
if(notDay == day && notHour == hour){
app.cancelLocalNotification(singleNotification)
break;
}
}
I used Int but it is a Dictionary

Related

Swift 4: Checking if week, month, 2 months, etc. has passed since initial launch using Calendar

I'm trying to include timers so that a button appears a week, a month, 2 months, etc. after the initial launch date of the app.
I've used Date.addingTimeInterval (see code below), but it doesn't seem as effective for longer periods of time (week/months). The Swift documentation recommends using Calendar, but I'm not sure how to do that.
override func viewDidLoad() {
super.viewDidLoad()
if installedDate == nil {
installedDate = Date()
let timer = Calendar.current
Button1.isHidden = true
Button2.isHidden = true
Button3.isHidden = true
}
else {
print("Not first run")
print(installedDate!)
let Button1Date = Date().addingTimeInterval(604800) //for after 1 week
let Button2Date = Date().addingTimeInterval(2592000) //for after 1 month
let Button3Date = Date().addingTimeInterval(5184000) //for after 2 months
if installedDate == Button1Date {
Button1.isHidden = false
}
if installedDate == Button2Date {
Button2.isHidden = false
}
if installedDate == Button3Date {
Button.isHidden = false
}
}
}
var installedDate: Date? {
get {
return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: "installedDateKey")
}
}
What is a more efficient way of comparing the launch date to the expected date I need?
From what I understand of your question, you want a button to display if they have had the app for more than a certain amount of time. If that is the case, I've written some code that will be helpful to you:
let timeSinceInstalled = -(installedDate ?? Date()).timeIntervalSinceNow
Button1.isHidden = timeSinceInstalled < 604800
Button2.isHidden = timeSinceInstalled < 2592000
Button3.isHidden = timeSinceInstalled < 5184000
You can actually replace most of the code inside of your viewDidLoad() function, even the part where you check if installedDate == nil. Here is your updated code:
override func viewDidLoad() {
super.viewDidLoad()
let timeSinceInstalled = -(installedDate ?? Date()).timeIntervalSinceNow
Button1.isHidden = timeSinceInstalled < 604800
Button2.isHidden = timeSinceInstalled < 2592000
Button3.isHidden = timeSinceInstalled < 5184000
}
var installedDate: Date? {
get {
return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: "installedDateKey")
}
}
If you want to set the title to "You have none available" when the button should not be clickable, here is the code for that:
override func viewDidLoad() {
super.viewDidLoad()
let timeSinceInstalled = -(installedDate ?? Date()).timeIntervalSinceNow
setTitle(Button1, timeSinceInstalled < 604800)
setTitle(Button2, timeSinceInstalled < 2592000)
setTitle(Button3, timeSinceInstalled < 5184000)
}
func setTitle(_ button: UIButton, _ statement: Bool) {
button.setTitle(statement ? "You have none available" : "Click me", for: .normal)
button.isEnabled = !statement
}
var installedDate: Date? {
get {
return UserDefaults.standard.object(forKey: "installedDateKey") as? Date
}
set {
UserDefaults.standard.set(newValue, forKey: "installedDateKey")
}
}

NSMetadataQuery isUpdating breaks after Reachability changes to none Swift 4

I have coded a UIViewController that handles the upload (Only) for files to iCloud. So far it works well but I was trying to make it better with Network Changes using Reachability.
The way I handle the changes is:
lazy var searchQuery:NSMetadataQuery = {
let searchQueryTemp = NSMetadataQuery()
searchQueryTemp.searchScopes = [NSMetadataQueryUbiquitousDocumentsScope]
let searchPredicate = NSPredicate.init(format: "%K BEGINSWITH %# && NOT %K.pathExtension = ''", argumentArray: [NSMetadataItemPathKey,trackFileManager.appICloudExportedMusic!.path,NSMetadataItemFSNameKey])
searchQueryTemp.predicate = searchPredicate
return searchQueryTemp
}()
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
print("\(logClassName): viewWillAppear")
appDelegate.appReachabilityDelegate = self
NotificationCenter.default.addObserver(self, selector: #selector(updateDataWithNotification), name: NSNotification.Name.NSMetadataQueryDidFinishGathering, object: searchQuery)
NotificationCenter.default.addObserver(self, selector: #selector(updateDataWithNotification), name: NSNotification.Name.NSMetadataQueryDidUpdate, object: searchQuery)
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateSearch()
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
print("\(logClassName): viewWillDisappear")
NotificationCenter.default.removeObserver(self)
}
#objc func updateDataWithNotification(notification: NSNotification){
print("\(logClassName): updateDataWithNotification \(searchQuery.results.count)")
if let queryObject = notification.object as? NSMetadataQuery{
if searchQuery == queryObject{
print("\(logClassName): Query count = \(queryObject.results.count)")
var iCloudAlbumArrayTemp = [FileIcloudAlbumHeader]()
/* Get the query results [URL] only files */
for result in queryObject.results{
/* Get Metadata for file */
if let metadataItem = result as? NSMetadataItem{
if let urlItem:URL = metadataItem.value(forKey: NSMetadataUbiquitousItemURLInLocalContainerKey) as? URL{
if var urlItemPath = urlItem.path.components(separatedBy: "Exported Music").last{
urlItemPath = String(urlItemPath.dropFirst())
let urlItemArray = urlItemPath.components(separatedBy: "/")
if urlItemArray.count == 3{
var albumICloudTrack = AlbumICloudTrack(url: urlItem)
let isUpdated:Bool = metadataItem.value(forKey: NSMetadataUbiquitousItemIsUploadedKey) as! Bool
let isUpdating: Bool = metadataItem.value(forKey: NSMetadataUbiquitousItemIsUploadingKey) as! Bool
let isDownloading:Bool = metadataItem.value(forKey: NSMetadataUbiquitousItemIsDownloadingKey) as! Bool
if isUpdated{
albumICloudTrack.status = .available
}
else{
if isUpdating{
let perInt = Int(metadataItem.value(forKey: NSMetadataUbiquitousItemPercentUploadedKey) as! Double)
print("\(logClassName): isUpdating: PerInt = \(perInt)")
let perDouble = metadataItem.value(forKey: NSMetadataUbiquitousItemPercentUploadedKey) as! Double
print("\(logClassName): isUpdating: PerInt = \(perDouble)")
albumICloudTrack.percentatge = Int(metadataItem.value(forKey: NSMetadataUbiquitousItemPercentUploadedKey) as! Double)
albumICloudTrack.status = .updating
}
else if isDownloading{
albumICloudTrack.status = .downloading
}
else{
albumICloudTrack.status = .notAvailable
}
}
/* Find Album */
var tempUrl = urlItem.deletingLastPathComponent()
let albumName = tempUrl.lastPathComponent
tempUrl = tempUrl.deletingLastPathComponent()
let artistName = tempUrl.lastPathComponent
//print("\(logClassName): Artist Name = \(artistName) && Album Name = \(albumName)")
let albumHeaderInex = findAlbumHeader(withArtistName: artistName, andAlbum: albumName, in: iCloudAlbumArrayTemp)
if albumHeaderInex != -1{
//print("\(logClassName): Appending Already exists")
iCloudAlbumArrayTemp[albumHeaderInex].urlTrackArray.append(albumICloudTrack)
}
else{
//print("\(logClassName): Creating New Header Album")
var albumHeader = FileIcloudAlbumHeader(artistName: artistName, albumName: albumName, url: urlItem.deletingLastPathComponent())
albumHeader.urlTrackArray.append(albumICloudTrack)
iCloudAlbumArrayTemp.append(albumHeader)
}
}
else{
print("\(logClassName): Discarting Item = \(urlItemPath)")
}
}
}
}
}
/* Copy content for updating Expanded status */
for iCloudAlbumIndex in iCloudAlbumArray.indices{
for iCloudAlbumTempIndex in iCloudAlbumArrayTemp.indices{
if iCloudAlbumArray[iCloudAlbumIndex].artistName == iCloudAlbumArrayTemp[iCloudAlbumTempIndex].artistName && iCloudAlbumArray[iCloudAlbumIndex].albumName == iCloudAlbumArrayTemp[iCloudAlbumTempIndex].albumName{
iCloudAlbumArrayTemp[iCloudAlbumTempIndex].isSelected = iCloudAlbumArray[iCloudAlbumIndex].isSelected
}
}
}
iCloudAlbumArray.removeAll()
for iCloudAlbumTempIndex in iCloudAlbumArrayTemp.indices{
iCloudAlbumArray.append(iCloudAlbumArrayTemp[iCloudAlbumTempIndex])
iCloudAlbumArray[iCloudAlbumTempIndex].urlTrackArray.sort {
return $0.trackName < $1.trackName
}
}
/* Reload table */
iCloudExportsTableView.reloadData()
}
}
}
The main problem here is that I see the file is updating in "Files" but
let isUpdating: Bool = metadataItem.value(forKey: NSMetadataUbiquitousItemIsUploadingKey) as! Bool
returns false
What am I not taking in consideration?
Thank you in advance
I have realised that despite NSMetadataUbiquitousItemIsUploadingKey returns false, NSMetadataUbiquitousItemPercentUploadedKey stills returns a number so:
let perDouble = metadataItem.value(forKey: NSMetadataUbiquitousItemPercentUploadedKey) as? Double ?? 100.00
if isUpdated{
albumICloudTrack.status = .available
}
else{
if isUpdating || perDouble < 100.00{
print("\(logClassName): isUpdating: perDouble = \(perDouble)")
albumICloudTrack.percentatge = Int(metadataItem.value(forKey: NSMetadataUbiquitousItemPercentUploadedKey) as! Double)
albumICloudTrack.status = .updating
}
Any other thoughts?

Countdown timer in table view cell shows different values after scrolling

The problem is described in title, but to be more specific here is a full picture.
I have a custom table view cell subclass with label inside it displaying the countdown timer. When there a small portion of timers it works fine, but with a lot of data I need to display timers far beyond the visible cells and when I scroll down fast and then scroll up fast, the timer values in cells start to show different values until a certain point in time, after which it shows the right value.
I tried different variants for those reuseable cells, but I can’t spot a problem. Help needed!!!
Here is the code of implementation of logic.
Custom cell subclass:
let calendar = Calendar.current
var timer: Timer?
var deadlineDate: Date? {
didSet {
updateTimeLabel()
}
}
override func awakeFromNib() {
purchaseCellCardView.layer.cornerRadius = 10
let selectedView = UIView(frame: CGRect.zero)
selectedView.backgroundColor = UIColor.clear
selectedBackgroundView = selectedView
}
override func prepareForReuse() {
super.prepareForReuse()
if timer != nil {
print("Invalidated!")
timer?.invalidate()
timer = nil
}
}
func configure(for purchase: Purchase) {
purchaseSubjectLabel.text = purchase.subject
startingPriceLabel.text = purchase.NMC
stageLabel.text = purchase.stage
fzImageView.image = purchase.fedLaw.contains("44") ? #imageLiteral(resourceName: "FZ44") : #imageLiteral(resourceName: "FZ223")
timeLabel.isHidden = purchase.stage == "Работа комиссии"
warningImageView.image = purchase.warningImage
}
func updateTimeLabel() {
setTimeLeft()
timer = Timer(timeInterval: 1, repeats: true) { [weak self] _ in
guard let strongSelf = self else {return}
strongSelf.setTimeLeft()
}
RunLoop.current.add(timer!, forMode: .commonModes)
}
#objc private func setTimeLeft() {
let currentDate = getCurrentLocalDate()
if deadlineDate?.compare(currentDate) == .orderedDescending {
var components = calendar.dateComponents([.day, .hour, .minute, .second], from: currentDate, to: deadlineDate!)
let dayText = (components.day! == 0 || components.day! < 0) ? "" : String(format: "%i", components.day!)
let hourText = (components.hour == 0 || components.hour! < 0) ? "" : String(format: "%i", components.hour!)
switch (dayText, hourText) {
case ("", ""):
timeLabel.text = String(format: "%02i", components.minute!) + ":" + String(format: "%02i", components.second!)
case ("", _):
timeLabel.text = hourText + " ч."
default:
timeLabel.text = dayText + " дн."
}
} else {
stageLabel.text = "Работа комиссии"
timeLabel.text = ""
timeLabel.isHidden = true
timer?.invalidate()
}
}
private func getCurrentLocalDate() -> Date {
var now = Date()
var nowComponents = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: now)
nowComponents.timeZone = TimeZone(abbreviation: "UTC")
now = calendar.date(from: nowComponents)!
return now
}
deinit {
print("DESTROYED")
timer?.invalidate()
timer = nil
}
The most important part of tableView(_cellForRowAt:)
case .results:
if filteredArrayOfPurchases.isEmpty {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.nothingFoundCell,
for: indexPath)
let label = cell.viewWithTag(110) as! UILabel
switch segmentedControl.index {
case 1:
label.text = "Нет закупок способом\n«Запрос предложений»"
case 2:
label.text = "Нет закупок способом\n«Конкурс»"
case 3:
label.text = "Нет закупок способом\n«Аукцион»"
default:
label.text = "Нет закупок способом\n«Запрос котировок»"
}
return cell
} else {
let cell = tableView.dequeueReusableCell(
withIdentifier: TableViewCellIdentifiers.purchaseCell,
for: indexPath) as! PurchaseCell
cell.containerViewTopConstraint.constant = indexPath.row == 0 ? 8.0 : 4.0
cell.containerViewBottomConstraint.constant = indexPath.row == filteredArrayOfPurchases.count - 1 ? 8.0 : 4.0
let purchase = filteredArrayOfPurchases[indexPath.row]
cell.configure(for: purchase)
if cell.timer != nil {
cell.updateTimeLabel()
} else {
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL, purchase.fedLaw, purchase.stage, completion: { (date) in
cell.deadlineDate = date
})
}
return cell
}
And the last piece of a puzzle:
func getDeadlineDateAndTimeToApply(_ url: URL?, _ fedLaw: String, _ stage: String, completion: #escaping (Date) -> ()) {
var deadlineDateAndTimeToApply = Date()
guard stage != "Работа комиссии" else { return }
if let url = url {
dataTask = URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
if let error = error as NSError?, error.code == -403 {
// TODO: Add alert here
return
}
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let data = data, let html = String(data: data, encoding: .utf8), let purchasePageBody = try? SwiftSoup.parse(html), let purchaseCard = try? purchasePageBody.select("td").array() else {return}
let mappedArray = purchaseCard.map(){String(describing: $0)}
if fedLaw.contains("44") {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td class=\"fontBoldTextTd\">Дата и время окончания подачи заявок</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[1]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
} else {
guard let deadlineDateToApplyString = try? purchaseCard[(mappedArray.index(of: "<td>Дата и время окончания подачи заявок<br> (по местному времени заказчика)</td>"))! + 1].text().components(separatedBy: " ") else {return}
dateFormatter.dateFormat = "dd.MM.yyyy HH:mm"
let deadlineDateToApply = deadlineDateToApplyString.first!
let deadlineTimeToApply = deadlineDateToApplyString[2]
guard let deadlineDateAndTimeToApplyCandidate = dateFormatter.date(from: "\(deadlineDateToApply) \(deadlineTimeToApply)") else {return}
deadlineDateAndTimeToApply = deadlineDateAndTimeToApplyCandidate
}
DispatchQueue.main.async {
completion(deadlineDateAndTimeToApply)
}
})
dataTask?.resume()
}
}
A few notes:
Tried resetting deadlineDate to nil in prepareForReuse() - doesn’t help;
Using SwiftSoup Framework to parse HTML as you can see in the last code example if it matters.
This is quite a lot of code but from what you are describing your issue is in reusing cells.
You would do well to separate the timers out of the cells and put them inside your objects. It is where they belong (or in some manager like view controller). Imagine having something like the following:
class MyObject {
var timeLeft: TimeInterval = 0.0 {
didSet {
if timeLeft > 0.0 && timer == nil {
timer = Timer.scheduled...
} else if timeLeft <= 0.0, let timer = timer {
timer.invalidate()
self.timer = nil
}
delegate?.myObject(self, updatedTimeLeft: timeLeft)
}
}
weak var delegate: MyObjectDelegate?
private var timer: Timer?
}
Now all you need is is a cell for row at index path to assign your object: cell.myObject = myObjects[indexPath.row].
And your cell would do something like:
var myObject: MyObject? {
didSet {
if oldValue.delegate == self {
oldValue.delegate = nil // detach from previous item
}
myObject.delegate = self
refreshUI()
}
}
func myObject(_ sender: MyObject, updatedTimeLeft timeLeft: TimeInterval) {
refreshUI()
}
I believe the rest should be pretty much straight forward...
Your problem is here:
search.getDeadlineDateAndTimeToApply(purchase.purchaseURL,
purchase.fedLaw,
purchase.stage,
completion: { (date) in
cell.deadlineDate = date
})
getDeadlineDateAndTimeToApply runs asynchronously, calculates something, and then updates the cell.deadlineData in the main thread (which is fine). But in the meantime, while calculating something, the user might have scrolled up and down, the cell might have been reused for another row, and now the update updates the cell incorrectly.
What you need to do is: Do not store the UITableViewCell directly. Instead, keep track of the IndexPath to be updated, and once the caluclation is done, retrieve the the cell that belongs to that IndexPath and update this.

Count-Up and Count-Down Timer with Date() in Swift 4 should stop after a certain time period

I am quite new to Swift and already learned a lot using the questions here.
In one of my first projects I try to write a soccer playtime timer app. The first timer is counting up after the whistle button is pressed showing the minutes played and the second timer is counting down to zero showing the minutes left to play. This works so far.
Now both timers should stop automatically when the halftime is over, so that I can start a third overtime timer. So far the invalidate statement of the timer is not working - both timers keep running. There seems to be something wrong with my if-statements, but at the moment I have no clue what. So any help would be very appreciated.
var countUpClock: Timer?
var countDownClock: Timer?
private var formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .positional
formatter.allowedUnits = [.minute, .second]
formatter.zeroFormattingBehavior = .pad
return formatter
}()
func runPlaytimeClocks() {
let startTime = Date()
let countTime = Date() + 2700 //45min of playtime
if startTime <= countTime {
countUpClock = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.timePlayed.text = self?.formatter.string(from: startTime, to: Date())
}
}
else {
countUpClock?.invalidate()
}
if startTime <= countTime {
countDownClock = Timer.scheduledTimer(withTimeInterval: -1.0, repeats: true) { [weak self] _ in
self?.timetoPlay.text = self?.formatter.string(from: Date(), to: countTime)
}
}
else {
countDownClock?.invalidate()
}
Thank you very much for the replies.
I found exactly what I was looking for here: http://ioscake.com/swift-nstimer-in-background.html
I adapted the solution to my for clocks (CountUpClock, CountDownClock, OvertimeClock, HalftimeClock).
Do you have any suggestions what would be the best solution to start the second halftime of the soccer game?
So far the CountUpClock starts again at 0:00 when I press the whistle button after the halftime break. But it should keep running from minute 45:00 to 90:00 - while the CountDownClock should counting down from 45:00 to 0:00 again.
What would be the best solution for such a behaviour?
import UIKit
import UserNotifications
private let stopTimeKey = "stopTimeKey"
class ViewController: UIViewController {
//Outlets
#IBOutlet weak var timePlayed: UILabel!
#IBOutlet weak var timeToPlay: UILabel!
#IBOutlet weak var overtime: UILabel!
#IBOutlet weak var halftime: UILabel!
#IBOutlet weak var halftimeButton: UIButton!
private var stopTime: Date?
override func viewDidLoad() {
super.viewDidLoad()
registerForLocalNotifications()
stopTime = UserDefaults.standard.object(forKey: stopTimeKey) as? Date
if let time = stopTime {
if time > Date() {
startTimers(time, includeNotification: false)
} else {
notifyTimerCompleted()
}
}
}
private func registerForLocalNotifications() {
if #available(iOS 10, *) {
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, error in
guard granted && error == nil else {
// display error
print("\(String(describing: error))")
return
}
}
} else {
let types: UIUserNotificationType = [.badge, .sound, .alert]
let settings = UIUserNotificationSettings(types: types, categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
}
//Actions
#IBAction func whistleButtonTapped(_ sender: UIButton) {
overtimeClock?.invalidate()
overtimeClock = nil
halftimeClock?.invalidate()
halftimeClock = nil
overtime.text = "00:00"
halftime.text = "00:00"
halftimeButton.isHidden = true
//add 10 seconds per halftime to try out
let time = Date() + 10
if time > Date() {
startTimers(time)
} else {
timeToPlay.text = "error"
}
}
#IBAction func halftimeButton(_ sender: UIButton) {
halftimeButtoPressed()
}
// Code for different Timers
private var countDownClock: Timer?
private var countUpClock: Timer?
var overtimeClock: Timer?
var halftimeClock: Timer?
private func startTimers(_ stopTime: Date, includeNotification: Bool = true) {
// save `stopTime` in case app is terminated
UserDefaults.standard.set(stopTime, forKey: stopTimeKey)
self.stopTime = stopTime
// start Timer
countDownClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountDownTimer(_:)), userInfo: nil, repeats: true)
countUpClock = Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(handleCountUpTimer(_:)), userInfo: nil, repeats: true)
guard includeNotification else { return }
// start local notification (so we're notified if timer expires while app is not running)
if #available(iOS 10, *) {
let content = UNMutableNotificationContent()
content.title = "Overtime is starting soon"
content.body = "In 5 seconds the overtime will start"
content.sound = UNNotificationSound.default()
//5 seconds warning before overtime starts
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: stopTime.timeIntervalSinceNow - 5, repeats: false)
let notification = UNNotificationRequest(identifier: "timer", content: content, trigger: trigger)
UNUserNotificationCenter.current().add(notification)
} else {
let notification = UILocalNotification()
//5 seconds warning before overtime starts
notification.fireDate = stopTime - 5
notification.alertBody = "Overtime is starting soon"
UIApplication.shared.scheduleLocalNotification(notification)
}
}
private func stopTimer() {
countDownClock?.invalidate()
countDownClock = nil
countUpClock?.invalidate()
countUpClock = nil
}
private func halftimeButtoPressed() {
overtimeClock?.invalidate()
overtimeClock = nil
startHalftimeClock()
halftimeButton.isHidden = true
}
private let dateComponentsFormatter: DateComponentsFormatter = {
let _formatter = DateComponentsFormatter()
_formatter.allowedUnits = [.minute, .second]
_formatter.unitsStyle = .positional
_formatter.zeroFormattingBehavior = .pad
return _formatter
}()
#objc func handleCountDownTimer(_ timer: Timer) {
let now = Date()
if stopTime! > now {
timeToPlay.text = dateComponentsFormatter.string(from: now, to: stopTime!)
} else {
stopTimer()
notifyTimerCompleted()
startOvertimeClock()
halftimeButton.isHidden = false
}
}
#objc func handleCountUpTimer(_ timer: Timer) {
//add 10 seconds per halftime to try out
let now = Date() + 10
if now > stopTime! {
timePlayed.text = dateComponentsFormatter.string(from: stopTime!, to: now)
} else {
stopTimer()
notifyTimerCompleted()
}
}
//Overtime Clock
#objc func startOvertimeClock() {
let startOvertime = Date()
overtimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.overtime.text = self?.dateComponentsFormatter.string(from: startOvertime, to: Date())
}
}
//Halftime Clock
#objc func startHalftimeClock() {
let startHalftime = Date()
halftimeClock = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] _ in
self?.halftime.text = self?.dateComponentsFormatter.string(from: startHalftime, to: Date())
}
}
private func notifyTimerCompleted() {
timeToPlay.text = "End"
timePlayed.text = "End"
}
}

Swift: Schedule a local notification for a specific time on weekdays

Hi I am trying to schedule a local notification to fire at 9:00am on weekdays (mon - fri) and cant seem to find any documentation on how to do this. Here is my code so far:
#IBAction func scheduleLocal(sender: UIButton) {
guard let settings = UIApplication.sharedApplication().currentUserNotificationSettings() else { return
}
if settings.types == .None {
let ac = UIAlertController(title: "Cant Schedule", message: "No Permission", preferredStyle: .Alert)
ac.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
presentViewController(ac, animated: true, completion: nil)
return
}
let notification = UILocalNotification()
notification.fireDate = NSDate()
notification.alertBody = "Come Exercise"
notification.alertAction = "Exercise Time"
notification.soundName = UILocalNotificationDefaultSoundName
notification.userInfo = ["customField1": "w00t"]
UIApplication.sharedApplication().scheduleLocalNotification(notification)
}
and in viewDidLoad:
let notificationSettings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil)
UIApplication.sharedApplication().registerUserNotificationSettings(notificationSettings)
This is my class example you can use it as you want , i also support Arabic language by translate Arabic number to English .
//
// notification.swift
// NSFetchController Pro
//
// Created by Mr.Geeker on 08/02/15.
// Copyright (c) 2015 X2coder. All rights reserved.
//
import UIKit
class notification {
func createNotification(#hour:String, day:String, msgBody:String)->Bool
{
if(self.checkifNotificationOnOrOff())
{
//println("\(araToString(hour))")
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.locale = NSLocale.currentLocale()
dateFormatter.dateStyle = NSDateFormatterStyle.NoStyle
dateFormatter.timeStyle = NSDateFormatterStyle.ShortStyle
var convertedTime:String = ""
var testDate:NSDate = NSDate()
var timeFormat:NSString = dateFormatter.stringFromDate(testDate)
// Get the range of date (AM OR PM )
var amRange:NSRange = timeFormat.rangeOfString(dateFormatter.AMSymbol)
var pmRange:NSRange = timeFormat.rangeOfString(dateFormatter.PMSymbol)
var is24h:Bool = (amRange.location == NSNotFound && pmRange.location == NSNotFound)
if is24h {
araToString(hour)
}else{
var dateFormatter2:NSDateFormatter = NSDateFormatter()
dateFormatter2.dateFormat = "HH:mm"
var hourDate:NSDate = dateFormatter2.dateFromString(hour)!
var pmAmDateString:NSString = dateFormatter2.stringFromDate(hourDate)
convertedTime = pmAmDateString
println(convertedTime)
}
var hourAndMiOnly:NSArray = convertedTime.componentsSeparatedByString(":")
var onlyHour:Int = 0
var onlyMinuts:Int = 0
for var i:Int = 0; i < 2; i++ {
for thimes in hourAndMiOnly
{
if i == 0
{
onlyMinuts = thimes.integerValue
}
if i == 1 {
// we should add -1 before the class
onlyHour = (thimes.integerValue)
if onlyHour < 1
{
onlyHour = 00
}
break
}
}
}
let DaysDic = [0:"None",1:"Sunday",2 :"Monday",3 :"Tuesday",4 :"Wednesday",5 :"Thursday",6 :"Friday",7 :"Saturday"]
var ConvertDayToInt:Int?{
for (key,value) in DaysDic
{
if value == day
{
return key
}
}
return nil
}
//println("Hour is \(onlyHour) And Minut \(onlyMinuts) AND Day \(ConvertDayToInt)")
let date = NSDate()
var userInfo: [NSObject : AnyObject]?
var createCalender:NSCalendar = NSCalendar(calendarIdentifier: NSGregorianCalendar)!
var dateComponenet:NSDateComponents = createCalender.components(NSCalendarUnit.YearCalendarUnit | NSCalendarUnit.WeekCalendarUnit, fromDate: date)
dateComponenet.weekday = ConvertDayToInt!
dateComponenet.hour = onlyHour
dateComponenet.minute = onlyMinuts
var fireDateToRun:NSDate = createCalender.dateFromComponents(dateComponenet)!
var notificationClass:UILocalNotification = UILocalNotification()
notificationClass.alertBody = msgBody
notificationClass.fireDate = fireDateToRun
notificationClass.soundName = UILocalNotificationDefaultSoundName
notificationClass.repeatInterval = NSCalendarUnit.WeekCalendarUnit
notificationClass.timeZone = NSTimeZone.defaultTimeZone()
notificationClass.applicationIconBadgeNumber = UIApplication.sharedApplication().applicationIconBadgeNumber + 1
UIApplication.sharedApplication().scheduledLocalNotifications = [notificationClass]
}else{
// call protocol function not support notification
}
return true
}
func araToString(numericString:String)->NSString
{
var nsMutString:NSMutableString = NSMutableString(string: numericString)
let ArabicString:NSString = "١٢٣٤٥٦٧٨٩:"
let EnglishString:NSString = "123456789:"
for ( var i:Int = 0 ; i < ArabicString.length ; i++)
{
var a:NSString = ArabicString.substringWithRange(NSMakeRange(i, 1))
var w:NSString = EnglishString.substringWithRange(NSMakeRange(i, 1))
nsMutString.replaceOccurrencesOfString(a, withString: w, options: NSStringCompareOptions.LiteralSearch, range:NSMakeRange(0, nsMutString.length))
}
return nsMutString
}
func deleteAll(){
let appnoti = UIApplication.sharedApplication()
let delArray = appnoti.scheduledLocalNotifications
if delArray.count > 0
{
appnoti.cancelAllLocalNotifications()
}
}
func checkifNotificationOnOrOff()->Bool
{
let systemVer:String = UIDevice.currentDevice().systemVersion
var finalFloat:Float = NSString(string: systemVer).floatValue
if finalFloat < 8 {
return false
}else{
return true
}
}
}

Resources