使用开关设置时间间隔的问题
Problem Using a Switch to Set A TimeInterval
我很难创建用户设置选项。我希望用户自定义定时器接收本地通知的频率。我在 SystemSettingsVC 上使用一个开关将用户设置为 select 并设置用户默认值,我在我的 MainVC 中使用用户默认设置作为 TimerInterval。我的应用程序运行但时间没有改变。我知道开关可以正常工作,因为我也在测试背景颜色的变化。
这是我的 SystemSettingsVC 代码:
...
import UIKit
import SwiftUI
import CoreData
class SettingsViewController: UIViewController {
@IBOutlet weak var timeSelection: UISegmentedControl!
let userDefaults = UserDefaults.standard
let TIME_KEY = "TIME_KEY"
let ONE_HOUR_KEY = 60.0
let THREE_HOUR_KEY = 120.0
let SIX_HOUR_KEY = 300.0
override func viewDidLoad() {
super.viewDidLoad()
updateTime()
}
func updateTime() {
let time = userDefaults.object(forKey: "TIME_KEY")
if(time as? Double == ONE_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 0
let userDefaults = UserDefaults.standard
userDefaults.set(60.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.white
save()
}
else if(time as? Double == THREE_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 1
let userDefaults = UserDefaults.standard
userDefaults.set(120.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.gray
save()
}
else if(time as? Double == SIX_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 2
let userDefaults = UserDefaults.standard
userDefaults.set(300.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.darkGray
save()
}
}
func save() {
if let savedData = try? NSKeyedArchiver.archivedData(withRootObject: clock, requiringSecureCoding: false){
let defaults = UserDefaults.standard
defaults.set(savedData, forKey: "TIME_KEY")
}
}
@IBAction func selectTimeOfQuotes(_ sender: Any) {
switch timeSelection.selectedSegmentIndex
{
case 0:
userDefaults.set(60.0, forKey: "TIME_KEY")
save()
case 1:
userDefaults.set(120.0, forKey: "TIME_KEY")
save()
case 2:
userDefaults.set(300.0, forKey: "TIME_KEY")
save()
default:
userDefaults.set(60.0, forKey: "TIME_KEY")
save()
}
updateTime()
}
}
...
这是我的视图控制器的代码,我在其中调用用户默认值,我将 let userDefaults = UserDefaults.standard 放在我的 ViewDidLoad 中:
'''Code''' ```
func configureAlerts() {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications()
center.removeAllPendingNotificationRequests()
let listQuotes = quotes
let i = 1
let content = UNMutableNotificationContent()
content.title = “Inspire”
content.body = listQuotes[i].shareMessage
content.sound = UNNotificationSound.default
let alertDate = Date().byAdding(days: i)
var alertComponents = Calendar.current.dateComponents([.day, .month, .year], from: alertDate)
alertComponents.hour = 8
let userDefaults = UserDefaults.standard
typealias NSTimeInterval = Double
let thisTime:TimeInterval = userDefaults.double(forKey: "TIME_KEY")
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: thisTime, repeats: true)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
您没有显示您的模型是如何连接的,因此我们无法判断哪里发生了错误传达,或者这可能就是问题所在。他们没有联系。
但乍一看,您并没有重新安排通知。 selectTimeOfQuotes
、save
或 updateTime
不要调用 configureAlerts
。
需要注意的是,您有很多重复代码和硬编码值,这可能是造成混淆的原因。
BTW 120 是 2 小时而不是 3 idk 如果这是故意的但它突出了我的下一个观点。
当你改变一个值时,你只想在一个地方做;如果可能的话;因此集中模型将帮助您避免在多个地方进行更改。
对于选择器的选项,enum
可以容纳所有内容。
enum NotificationInterval: Double, CaseIterable, Codable{
case ONE_HOUR_KEY = 3660 //TimeInterval == seconds
case THREE_HOUR_KEY = 10800 //TimeInterval == seconds
case SIX_HOUR_KEY = 21600 //TimeInterval == seconds
func label() -> String{
var result = ""
switch self {
case .ONE_HOUR_KEY:
result = "1 hour"
case .THREE_HOUR_KEY:
result = "3 hours"
case .SIX_HOUR_KEY:
result = "6 hours"
}
return result
}
func color() -> UIColor{
var result = UIColor.label
switch self {
case .ONE_HOUR_KEY:
result = UIColor.white
case .THREE_HOUR_KEY:
result = UIColor.gray
case .SIX_HOUR_KEY:
result = UIColor.darkGray
}
return result
}
///Key for storage of user selected interval
static var userDefaultKey: String{
"TIME_KEY"
}
///Saves value to store using the `userDefaultKey`
func saveToStore(){
var mgr = UserDefaultManager()
mgr.intervalTime = self
}
///Gets value from store using the `userDefaultKey`
static func getFromStore() -> NotificationInterval{
let raw = UserDefaultManager().intervalTime
return raw
}
///Gets the index for the object in the `allCases` array
func getAllCasesIndex() -> Int?{
NotificationInterval.allCases.firstIndex(where: {
self == [=10=]
})
}
///Gets the index for the `userDefaultKey` stored object in the `allCases` array
static func getStoredIndex() -> Int?{
NotificationInterval.getFromStore().getAllCasesIndex()
}
}
然后,由于您至少有 2 个不相关的 类 使用用户默认值中的值存储,您也可以集中处理该工作
///This stores and retreives userdefaults to a predetermined store
struct UserDefaultManager{
//Having a single location for this will simplify UserDefault storage
//A use case would be switching to an App Group store when you decide to support watch in the future or if you want to add Widgets
private let store = UserDefaults.standard
///User selected interval for the notifications
var intervalTime: NotificationInterval{
get{
getObject(forKey: NotificationInterval.userDefaultKey, type: NotificationInterval.self) ?? NotificationInterval.ONE_HOUR_KEY
}
set{
save(newValue, forKey: NotificationInterval.userDefaultKey)
}
}
///Saves any Codable to UserDefaults
func save<T: Codable>(_ object: T, forKey: String){
let encoder = JSONEncoder()
do{
let encoded = try encoder.encode(object)
store.set(encoded, forKey: forKey)
}catch{
print(error)
}
}
//Gets any Codable from UserDefaults
func getObject<T: Codable>(forKey: String, type: T.Type) -> T?{
guard let saved = store.object(forKey: forKey) as? Data else {
return nil
}
let decoder = JSONDecoder()
do{
let loaded = try decoder.decode(T.self, from: saved)
return loaded
}catch{
print(error)
return nil
}
}
}
那么你的 SettingsViewController
会像这样
class SettingsViewController: UIViewController {
///Programatic use of IBOutlet
var timeSelection: UISegmentedControl!
private let quoteManager = QuoteManager()
override func viewDidLoad() {
super.viewDidLoad()
//Create control PS I dont have a storyboard setup but you can replace this with your IBOutlet and IBAction
timeSelection = UISegmentedControl(items: NotificationInterval.allCases.map({
[=12=].label()
}))
timeSelection.addTarget(self, action: #selector(selectTimeOfQuotes), for: .allEvents)
//Set the initial value from storage
timeSelection.selectedSegmentIndex = NotificationInterval.getStoredIndex() ?? 0
self.view.addSubview(timeSelection)
timeSelection.translatesAutoresizingMaskIntoConstraints = false
timeSelection.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
timeSelection.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
//End of programatic setup
//Set the color from storage
view.backgroundColor = NotificationInterval.getFromStore().color()
}
///Programatic use of IBAction
@objc
func selectTimeOfQuotes() {
//Identify the selected interval
let interval = NotificationInterval.allCases[timeSelection.selectedSegmentIndex]
//Save it
interval.saveToStore()
//Change the color
view.backgroundColor = interval.color()
//Change the notification
quoteManager.rescheduleQuotes()
}
}
如最后一行代码所示,一旦所有工作完成,您应该重新安排报价。
我创建了一个迷你-QuoteManager
因为你没有显示这个连接。任何视图控制器都可以使用此管理器来获取报价,甚至可以在报价发生变化时通过调用提供的方法重新安排时间。
//Adapt this to your use case this is just a sample
///Liason for quote Storege
struct QuoteManager{
var listQuotes = ["one", "two", "three"]
private let notificationManager = NotificationManager.shared
private let userDefaultsManager = UserDefaultManager()
///Reschedules quotes
func rescheduleQuotes(count: Int = 10){
let title = "Inspire"
notificationManager.deleteNotifications()
print(#function)
for n in 1..<count+1{
print(n)
let newDate = userDefaultsManager.intervalTime.rawValue*Double(n)
//Idenfier must be unique so I added the n
notificationManager.scheduleUNTimeIntervalNotificationTrigger(title: title, body: listQuotes.randomElement()!, timeInterval: newDate, identifier: "com.yourCompany.AppName.\(title)_\(n.description)")
}
}
}
QuoteManager
调用NotificationManager
。我在下面创建了一个小版本。
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
//Singleton is requierd because of delegate
static let shared: NotificationManager = NotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
private override init(){
super.init()
//This assigns the delegate
notificationCenter.delegate = self
requestAuthorization()
}
func scheduleUNTimeIntervalNotificationTrigger(title: String, body: String, timeInterval: TimeInterval, identifier: String, repeats: Bool = false){
print(#function)
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: repeats)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
notificationCenter.add(request) { (error) in
if error != nil {
print(error!)
}
self.printNotifications()
}
}
func requestAuthorization() {
print(#function)
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Access Granted!")
} else {
print("Access Not Granted")
}
}
}
func deleteNotifications(){
print(#function)
notificationCenter.removeAllPendingNotificationRequests()
notificationCenter.removeAllDeliveredNotifications()
}
///Prints to console schduled notifications
func printNotifications(){
print(#function)
notificationCenter.getPendingNotificationRequests { request in
print("UNTimeIntervalNotificationTrigger Pending Notification")
for req in request{
if req.trigger is UNTimeIntervalNotificationTrigger{
print((req.trigger as! UNTimeIntervalNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
print(req.content.body)
}
}
print("UNCalendarNotificationTrigger Pending Notification")
for req in request{
if req.trigger is UNCalendarNotificationTrigger{
print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
}
}
}
}
//MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.banner)
}
}
可能看起来很多,但如果您专注于 SettingsViewController
,您会发现整个事情变得多么简单。
所有这些都是工作代码。只需复制并粘贴到 .swift
文件中即可。
您可能需要更改 UISegmentedControl
,因为我是通过编程方式创建它的,但是如果您将 SettingsViewController
放在空白故事板中,它应该可以按原样工作。
我很难创建用户设置选项。我希望用户自定义定时器接收本地通知的频率。我在 SystemSettingsVC 上使用一个开关将用户设置为 select 并设置用户默认值,我在我的 MainVC 中使用用户默认设置作为 TimerInterval。我的应用程序运行但时间没有改变。我知道开关可以正常工作,因为我也在测试背景颜色的变化。
这是我的 SystemSettingsVC 代码: ...
import UIKit
import SwiftUI
import CoreData
class SettingsViewController: UIViewController {
@IBOutlet weak var timeSelection: UISegmentedControl!
let userDefaults = UserDefaults.standard
let TIME_KEY = "TIME_KEY"
let ONE_HOUR_KEY = 60.0
let THREE_HOUR_KEY = 120.0
let SIX_HOUR_KEY = 300.0
override func viewDidLoad() {
super.viewDidLoad()
updateTime()
}
func updateTime() {
let time = userDefaults.object(forKey: "TIME_KEY")
if(time as? Double == ONE_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 0
let userDefaults = UserDefaults.standard
userDefaults.set(60.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.white
save()
}
else if(time as? Double == THREE_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 1
let userDefaults = UserDefaults.standard
userDefaults.set(120.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.gray
save()
}
else if(time as? Double == SIX_HOUR_KEY) {
timeSelection.selectedSegmentIndex = 2
let userDefaults = UserDefaults.standard
userDefaults.set(300.0, forKey: "TIME_KEY")
view.backgroundColor = UIColor.darkGray
save()
}
}
func save() {
if let savedData = try? NSKeyedArchiver.archivedData(withRootObject: clock, requiringSecureCoding: false){
let defaults = UserDefaults.standard
defaults.set(savedData, forKey: "TIME_KEY")
}
}
@IBAction func selectTimeOfQuotes(_ sender: Any) {
switch timeSelection.selectedSegmentIndex
{
case 0:
userDefaults.set(60.0, forKey: "TIME_KEY")
save()
case 1:
userDefaults.set(120.0, forKey: "TIME_KEY")
save()
case 2:
userDefaults.set(300.0, forKey: "TIME_KEY")
save()
default:
userDefaults.set(60.0, forKey: "TIME_KEY")
save()
}
updateTime()
}
}
...
这是我的视图控制器的代码,我在其中调用用户默认值,我将 let userDefaults = UserDefaults.standard 放在我的 ViewDidLoad 中: '''Code''' ```
func configureAlerts() {
let center = UNUserNotificationCenter.current()
center.removeAllDeliveredNotifications()
center.removeAllPendingNotificationRequests()
let listQuotes = quotes
let i = 1
let content = UNMutableNotificationContent()
content.title = “Inspire”
content.body = listQuotes[i].shareMessage
content.sound = UNNotificationSound.default
let alertDate = Date().byAdding(days: i)
var alertComponents = Calendar.current.dateComponents([.day, .month, .year], from: alertDate)
alertComponents.hour = 8
let userDefaults = UserDefaults.standard
typealias NSTimeInterval = Double
let thisTime:TimeInterval = userDefaults.double(forKey: "TIME_KEY")
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: thisTime, repeats: true)
let uuidString = UUID().uuidString
let request = UNNotificationRequest(identifier: uuidString, content: content, trigger: trigger)
center.add(request) { error in
if let error = error {
print(error.localizedDescription)
}
}
您没有显示您的模型是如何连接的,因此我们无法判断哪里发生了错误传达,或者这可能就是问题所在。他们没有联系。
但乍一看,您并没有重新安排通知。 selectTimeOfQuotes
、save
或 updateTime
不要调用 configureAlerts
。
需要注意的是,您有很多重复代码和硬编码值,这可能是造成混淆的原因。
BTW 120 是 2 小时而不是 3 idk 如果这是故意的但它突出了我的下一个观点。
当你改变一个值时,你只想在一个地方做;如果可能的话;因此集中模型将帮助您避免在多个地方进行更改。
对于选择器的选项,enum
可以容纳所有内容。
enum NotificationInterval: Double, CaseIterable, Codable{
case ONE_HOUR_KEY = 3660 //TimeInterval == seconds
case THREE_HOUR_KEY = 10800 //TimeInterval == seconds
case SIX_HOUR_KEY = 21600 //TimeInterval == seconds
func label() -> String{
var result = ""
switch self {
case .ONE_HOUR_KEY:
result = "1 hour"
case .THREE_HOUR_KEY:
result = "3 hours"
case .SIX_HOUR_KEY:
result = "6 hours"
}
return result
}
func color() -> UIColor{
var result = UIColor.label
switch self {
case .ONE_HOUR_KEY:
result = UIColor.white
case .THREE_HOUR_KEY:
result = UIColor.gray
case .SIX_HOUR_KEY:
result = UIColor.darkGray
}
return result
}
///Key for storage of user selected interval
static var userDefaultKey: String{
"TIME_KEY"
}
///Saves value to store using the `userDefaultKey`
func saveToStore(){
var mgr = UserDefaultManager()
mgr.intervalTime = self
}
///Gets value from store using the `userDefaultKey`
static func getFromStore() -> NotificationInterval{
let raw = UserDefaultManager().intervalTime
return raw
}
///Gets the index for the object in the `allCases` array
func getAllCasesIndex() -> Int?{
NotificationInterval.allCases.firstIndex(where: {
self == [=10=]
})
}
///Gets the index for the `userDefaultKey` stored object in the `allCases` array
static func getStoredIndex() -> Int?{
NotificationInterval.getFromStore().getAllCasesIndex()
}
}
然后,由于您至少有 2 个不相关的 类 使用用户默认值中的值存储,您也可以集中处理该工作
///This stores and retreives userdefaults to a predetermined store
struct UserDefaultManager{
//Having a single location for this will simplify UserDefault storage
//A use case would be switching to an App Group store when you decide to support watch in the future or if you want to add Widgets
private let store = UserDefaults.standard
///User selected interval for the notifications
var intervalTime: NotificationInterval{
get{
getObject(forKey: NotificationInterval.userDefaultKey, type: NotificationInterval.self) ?? NotificationInterval.ONE_HOUR_KEY
}
set{
save(newValue, forKey: NotificationInterval.userDefaultKey)
}
}
///Saves any Codable to UserDefaults
func save<T: Codable>(_ object: T, forKey: String){
let encoder = JSONEncoder()
do{
let encoded = try encoder.encode(object)
store.set(encoded, forKey: forKey)
}catch{
print(error)
}
}
//Gets any Codable from UserDefaults
func getObject<T: Codable>(forKey: String, type: T.Type) -> T?{
guard let saved = store.object(forKey: forKey) as? Data else {
return nil
}
let decoder = JSONDecoder()
do{
let loaded = try decoder.decode(T.self, from: saved)
return loaded
}catch{
print(error)
return nil
}
}
}
那么你的 SettingsViewController
会像这样
class SettingsViewController: UIViewController {
///Programatic use of IBOutlet
var timeSelection: UISegmentedControl!
private let quoteManager = QuoteManager()
override func viewDidLoad() {
super.viewDidLoad()
//Create control PS I dont have a storyboard setup but you can replace this with your IBOutlet and IBAction
timeSelection = UISegmentedControl(items: NotificationInterval.allCases.map({
[=12=].label()
}))
timeSelection.addTarget(self, action: #selector(selectTimeOfQuotes), for: .allEvents)
//Set the initial value from storage
timeSelection.selectedSegmentIndex = NotificationInterval.getStoredIndex() ?? 0
self.view.addSubview(timeSelection)
timeSelection.translatesAutoresizingMaskIntoConstraints = false
timeSelection.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
timeSelection.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
//End of programatic setup
//Set the color from storage
view.backgroundColor = NotificationInterval.getFromStore().color()
}
///Programatic use of IBAction
@objc
func selectTimeOfQuotes() {
//Identify the selected interval
let interval = NotificationInterval.allCases[timeSelection.selectedSegmentIndex]
//Save it
interval.saveToStore()
//Change the color
view.backgroundColor = interval.color()
//Change the notification
quoteManager.rescheduleQuotes()
}
}
如最后一行代码所示,一旦所有工作完成,您应该重新安排报价。
我创建了一个迷你-QuoteManager
因为你没有显示这个连接。任何视图控制器都可以使用此管理器来获取报价,甚至可以在报价发生变化时通过调用提供的方法重新安排时间。
//Adapt this to your use case this is just a sample
///Liason for quote Storege
struct QuoteManager{
var listQuotes = ["one", "two", "three"]
private let notificationManager = NotificationManager.shared
private let userDefaultsManager = UserDefaultManager()
///Reschedules quotes
func rescheduleQuotes(count: Int = 10){
let title = "Inspire"
notificationManager.deleteNotifications()
print(#function)
for n in 1..<count+1{
print(n)
let newDate = userDefaultsManager.intervalTime.rawValue*Double(n)
//Idenfier must be unique so I added the n
notificationManager.scheduleUNTimeIntervalNotificationTrigger(title: title, body: listQuotes.randomElement()!, timeInterval: newDate, identifier: "com.yourCompany.AppName.\(title)_\(n.description)")
}
}
}
QuoteManager
调用NotificationManager
。我在下面创建了一个小版本。
class NotificationManager: NSObject, UNUserNotificationCenterDelegate{
//Singleton is requierd because of delegate
static let shared: NotificationManager = NotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
private override init(){
super.init()
//This assigns the delegate
notificationCenter.delegate = self
requestAuthorization()
}
func scheduleUNTimeIntervalNotificationTrigger(title: String, body: String, timeInterval: TimeInterval, identifier: String, repeats: Bool = false){
print(#function)
let content = UNMutableNotificationContent()
content.title = title
content.body = body
content.sound = .default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: timeInterval, repeats: repeats)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
notificationCenter.add(request) { (error) in
if error != nil {
print(error!)
}
self.printNotifications()
}
}
func requestAuthorization() {
print(#function)
notificationCenter.requestAuthorization(options: [.alert, .badge, .sound]) { (granted, error) in
if granted {
print("Access Granted!")
} else {
print("Access Not Granted")
}
}
}
func deleteNotifications(){
print(#function)
notificationCenter.removeAllPendingNotificationRequests()
notificationCenter.removeAllDeliveredNotifications()
}
///Prints to console schduled notifications
func printNotifications(){
print(#function)
notificationCenter.getPendingNotificationRequests { request in
print("UNTimeIntervalNotificationTrigger Pending Notification")
for req in request{
if req.trigger is UNTimeIntervalNotificationTrigger{
print((req.trigger as! UNTimeIntervalNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
print(req.content.body)
}
}
print("UNCalendarNotificationTrigger Pending Notification")
for req in request{
if req.trigger is UNCalendarNotificationTrigger{
print((req.trigger as! UNCalendarNotificationTrigger).nextTriggerDate()?.description ?? "invalid next trigger date")
}
}
}
}
//MARK: UNUserNotificationCenterDelegate
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
completionHandler(.banner)
}
}
可能看起来很多,但如果您专注于 SettingsViewController
,您会发现整个事情变得多么简单。
所有这些都是工作代码。只需复制并粘贴到 .swift
文件中即可。
您可能需要更改 UISegmentedControl
,因为我是通过编程方式创建它的,但是如果您将 SettingsViewController
放在空白故事板中,它应该可以按原样工作。