Xcode 10.3
swift5
ios 12.4
swift5
ios 12.4
完成品
機能面を重視したのでデザインは特に意識していません
また、アラーム音もデフォルトしか設定していないので今回のサウンド設定は飾りです。
(ご要望があれば追加します。)
UI
実装の仕方はいろいろありますが、
今回はTableViewの上に独自Cellを乗せる設計にしました。
コード
AppDelegate
import UIKit import UserNotifications //UserNotificationsをimport @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0) func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // 通知許可の取得 UNUserNotificationCenter.current().requestAuthorization( options: [.alert, .sound, .badge]){ (granted, _) in if granted{ UNUserNotificationCenter.current().delegate = self } } return true } //バックグラウンド遷移移行直前に呼ばれる func applicationWillResignActive(_ application: UIApplication) { // 新しいタスクを登録 backgroundTaskID = application.beginBackgroundTask { [weak self] in application.endBackgroundTask((self?.backgroundTaskID)!) self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid } } //アプリがアクティブになる度に呼ばれる func applicationDidBecomeActive(_ application: UIApplication) { //タスクの解除 application.endBackgroundTask(self.backgroundTaskID) } } extension AppDelegate: UNUserNotificationCenterDelegate{ func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { // アプリ起動中でもアラートと音で通知 completionHandler([.alert, .sound]) } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { // バックグラウンドで来た通知をタップしてアプリ起動したら呼ばれる completionHandler() } }
TimerVC
import UIKit import UserNotifications //UserNotificationsをimport class TimerVC: UIViewController, UITableViewDelegate,UITableViewDataSource { @IBOutlet weak var tableView: UITableView! var timerOn:Bool = false var timerStart:Bool = false var time:Double = 0 override func viewDidLoad() { super.viewDidLoad() //cellを登録 registerCell(cellName: "TimerLabelCell") registerCell(cellName: "TimerSetCell") registerCell(cellName: "SoundSetCell") registerCell(cellName: "TimerButtonCell") } //cellを登録 func registerCell(cellName:String){ tableView.register(UINib(nibName: cellName, bundle: nil), forCellReuseIdentifier: cellName) } //セクションごとのセルの個数を指定 func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 1 } //セクション数 func numberOfSections(in tableView: UITableView) -> Int { return 3 } //セクションごとにセルの高さを設定 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { switch indexPath.section { case 0: return 220 case 1: return 44 case 2: return 160 default: return 0 } } // セクションごとにセル生成 func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { switch indexPath.section { case 0: //タイマーがスタートしているかどうか if timerStart { // タイマーがスタートしていた時TimerLabelCellを生成 let cell = tableView.dequeueReusableCell(withIdentifier: "TimerLabelCell") as! TimerLabelCell cell.delegate = self // タイマー動いているか if timerOn { cell.time = time cell.startTimer() }else{ cell.stopTimer() } return cell }else{ // タイマーがスタートしていない時TimerSetCellを生成 let cell = tableView.dequeueReusableCell(withIdentifier: "TimerSetCell") as! TimerSetCell cell.delegate = self cell.getTimer() return cell } case 1: let cell = tableView.dequeueReusableCell(withIdentifier: "SoundSetCell") as! SoundSetCell return cell case 2: let cell = tableView.dequeueReusableCell(withIdentifier: "TimerButtonCell") as! TimerButtonCell cell.delegate = self if !timerStart { cell.start() } return cell default: return UITableViewCell() } } } extension TimerVC:TimerLabelCellDelegate { func timerLabelCell(stopTimer: TimerLabelCell, remainingTime: Double) { time = remainingTime } func timerLabelCell(timerEnd alarmSet: TimerLabelCell) { timerStart = false timerOn = false tableView.reloadData() } } extension TimerVC:TimerSetCellDelegate { func timerSetCell(setTime: TimerSetCell, time: Double) { self.time = time } } extension TimerVC:TimerButtonCellDelegate { func timerButtonCell(startStopTime: TimerButtonCell) { //通知を設定 let center = UNUserNotificationCenter.current() center.removePendingNotificationRequests(withIdentifiers: ["Timer"]) if time != 0 { timerStart = true if timerOn { timerOn = false startStopTime.resume() }else{ timerOn = true startStopTime.pause() let content = UNMutableNotificationContent() content.sound = UNNotificationSound.default content.title = "Timer Done" let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(time), repeats: false) let identifier = "Timer" let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request){ (error : Error?) in if let error = error { print(error.localizedDescription) } } } }else{ timerStart = false startStopTime.start() } tableView.reloadData() } func timerButtonCell(cancelTime: TimerButtonCell) { timerStart = false timerOn = false cancelTime.start() let center = UNUserNotificationCenter.current() center.removePendingNotificationRequests(withIdentifiers: ["Timer"]) tableView.reloadData() } }
TimerLabelCell
import UIKit protocol TimerLabelCellDelegate { func timerLabelCell(timerEnd:TimerLabelCell) func timerLabelCell(stopTimer:TimerLabelCell,remainingTime:Double) } class TimerLabelCell: UITableViewCell { @IBOutlet weak var timerLabel: UILabel! var timer = Timer() var time:Double = 0 { didSet { //タイマーをtimerLabelにセット timerLabel.text = timeString(time: time) } } var delegate:TimerLabelCellDelegate! //タイマースタート func startTimer(){ //タイマーが動いているか if timer.isValid { timer.invalidate() } // タイマー生成、開始 1秒後の実行 timer = Timer.scheduledTimer( timeInterval: 1.0, // 時間間隔 target: self, // タイマーの実際の処理の場所 selector: #selector(TimerLabelCell.tickTimer(_:)), // メソッド タイマーの実際の処理 userInfo: nil, repeats: true) } //タイマー停止 func stopTimer(){ timer.invalidate() delegate.timerLabelCell(stopTimer: self, remainingTime: time) } // タイマー処理 @objc func tickTimer(_ timer: Timer) { time -= 1 timerLabel.text = timeString(time: time) //timeが-1になった時 if time == -1 { // タイマーの停止 timer.invalidate() delegate.timerLabelCell(timerEnd: self) } } //時分秒を取得 func timeString(time:TimeInterval) -> String { let hours = Int(time) / 3600 //時 let minutes = Int(time) / 60 % 60 //分 let seconds = Int(time) % 60 //秒 return String(format:"%02i:%02i:%02i", hours, minutes, seconds) } }
TimerSetCell
import UIKit protocol TimerSetCellDelegate { func timerSetCell(setTime: TimerSetCell, time: Double) } class TimerSetCell: UITableViewCell,UIPickerViewDataSource, UIPickerViewDelegate { @IBOutlet weak var pickerView: UIPickerView! var delegate:TimerSetCellDelegate! var time:Double = 0 var hour:Double = 0 //時 var minutes:Double = 0 //分 var seconds:Double = 0 //秒 let labelText = ["hours", "min", "sec"] var labels :[UILabel] = [] override func awakeFromNib() { super.awakeFromNib() pickerView.delegate = self pickerView.dataSource = self if time != 0 { delegate.timerSetCell(setTime: self, time: time) } } public override func layoutSubviews() { super.layoutSubviews() self.reloadAllComponentLabels() } //ピッカーに表示する列数 func numberOfComponents(in pickerView: UIPickerView) -> Int { return 3 } //ピッカーに表示する値(数字) func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { switch component { case 0: return 24 case 1,2: return 60 default: return 0 } } func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { switch component { //秒に変換 case 0: hour = Double(row * 3600) case 1: minutes = Double(row * 60) case 2: seconds = Double(row) default: break; } time = hour + minutes + seconds delegate.timerSetCell(setTime: self, time: time) } func timeString(time:TimeInterval) -> String { let hours = Int(time) / 3600 let minutes = Int(time) / 60 % 60 let seconds = Int(time) % 60 return String(format:"%i:%02i:%02i", hours, minutes, seconds) } func getTimer(){ delegate.timerSetCell(setTime: self, time: time) } //ピッカーに表示する値(文字列) func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return row.description } //列の幅 func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat { return pickerView.frame.size.width/4 } // ラベルの配置 public func reloadAllComponentLabels() { let fontSize = UIFont.systemFontSize let labelTop = self.bounds.origin.y + self.bounds.height / 2 - fontSize let labelHeight = self.pickerView.rowSize(forComponent: 0).height var labelOffset = self.bounds.origin.x // Componentの右端 for i in 0...(self.numberOfComponents(in: pickerView)) - 1 { if self.labels.count == i { let label = UILabel() label.text = labelText[i] label.backgroundColor = UIColor.clear label.font = UIFont.boldSystemFont(ofSize: fontSize) label.sizeToFit() self.addSubview(label) self.labels.append(label) } // ラベルの位置を決める let labelWidth = self.labels[i].frame.width labelOffset += pickerView.rowSize(forComponent: i).width self.labels[i].frame = CGRect(x: labelOffset - labelWidth + pickerView.rowSize(forComponent: i).width/2, y: labelTop, width: labelWidth, height: labelHeight) } } }
SoundSetCell
import UIKit class SoundSetCell: UITableViewCell { @IBOutlet weak var soundNameLabel: UILabel! }
TimerButtonCell
import UIKit protocol TimerButtonCellDelegate { func timerButtonCell(startStopTime : TimerButtonCell) func timerButtonCell(cancelTime : TimerButtonCell) } class TimerButtonCell: UITableViewCell { @IBOutlet weak var startButton: UIButton! @IBOutlet weak var cancelButton: UIButton! var delegate:TimerButtonCellDelegate! override func awakeFromNib() { super.awakeFromNib() cancelButton.isEnabled = true } override func setSelected(_ selected: Bool, animated: Bool) { super.setSelected(selected, animated: animated) } //開始停止 @IBAction func startstop(_ sender: Any) { delegate.timerButtonCell(startStopTime: self) } //キャンセル @IBAction func cancel(_ sender: Any) { delegate.timerButtonCell(cancelTime: self) cancelButton.isEnabled = true } //スタートボタンに変更 func start(){ startButton.setTitle("Start", for: .normal) startButton.backgroundColor = #colorLiteral(red: 0.1799043417, green: 0.6762347817, blue: 0.2553646266, alpha: 1) } //一時停止に変更 func pause(){ startButton.setTitle("Pause", for: .normal) startButton.backgroundColor = .orange } //再開に変更 func resume(){ startButton.setTitle("Resume", for: .normal) startButton.backgroundColor = #colorLiteral(red: 0.1799043417, green: 0.6762347817, blue: 0.2553646266, alpha: 1) } }githubはこちら
0 件のコメント:
コメントを投稿