swiftでiPhone標準の時計アプリを作ろう 完成コード タイマー

2019年9月6日金曜日

swift 時計

t f B! P L
Xcode 10.3
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はこちら

参考

QooQ