電子の海をたゆたう

iOS初心者がXcodeでアプリ開発を学んでいく日記です。

macアプリにチャレンジしてみた

swiftの勉強進めてます。

if文に括弧つけなくてよかったり、セミコロンを最後につけなくてよかったり

swiftに慣れすぎると別の言語を書く際に文法がごちゃごちゃになって大変なことになりそうです。

でも最近は簡略化してて逆に見やすいと感じるようにもなってきました。とりあえず勉強進めて実用レベルまで持って行きたいところです。

今回の投稿はタイマーを作る機会があったのでiPhoneアプリではなく、

macアプリを自分なりに作ってみたのでそれに関しての備忘録です。

macアプリ、開発者登録してないと起動時に怒られますね・・・。

でもリリースするわけではないのでとりあえずスルーしました。

配布もごく一部に限定的に行うのでシステム環境設定のセキュリティとプライバシーからダウンロードしたアプリケーションの実行許可をすべてのアプリケーションを許可にしてもらったりと、使ってもらう側にも設定してもらう必要ありでした。

では、簡単に本題に今回作ったのはニキシー管風味のタイマーアプリです。

完成したものはこれ

ビジュアルは割とよくできたかなと思ってます。

では簡単に手順を説明します。なにぶんmacアプリを作ったのが初めてなのでいろいろ間違ったところもあると思いますが、ご容赦ください。

というか、そもそもmacアプリに関する資料が少ない・・・検索をかけてもiPhoneアプリの記事ばかりでやりたいことと微妙に食い違いがあってアプリ作成には難航しました。

はい、話進めます。

まず最初にやったのはXcodeの新規Project作成でOS XCocoa Application>言語をSwiftにしてプロジェクトを作成。

前回で折角ストーリーボードを学んだのでどうせならと思い、UI周りは全てストーリーボード上で作成してみました。

AutoLayoutだいぶ使いこなせるようになったからmacアプリでも楽勝だろうと思ってた時期がわたしにもありました。Main.storyboarを開いてみたらSceneが二つある・・・。

どういうことなのと思いましたがどうやらUIを設置できるのは下にある方のView Controller Sceneの模様。上に設置されているWindow Contoller Sceneはどうやらヘッダー的なやつらしい。いわゆる「ファイル、編集、表示、履歴、ヘルプ」などを設定する箇所みたいです。起動とクローズができればよかったので今回は特にこちらは触らずにすすめました。

先に画像を使わない単純なタイマーを作ろうと思い、StartボタンとResetボタンを設置し、制限時間のTextFieldを設置しました。

これにTextFieldとButton類をViewContoller.swiftにアウトレット接続して、
ボタン類のみActionを作成します。


とりあえずタイマーとしては完成したのですが、上司からフィードバックをいただきどうせやるならもう少し凝りたいなと思い、
Photoshop ニキシー管風味カウンター素材 - trismegistuslabo

こちらのサイトから素材をお借りして、ニキシー管風タイマーを作ってみることに。

すごく原始的ですが、タイマーの文字列を1文字づつみて対応した数字の画像で更新し続けるというプログラムを書いてみました。

作成したコードがこちら
ViewContoller.swift

import Cocoa
import AVFoundation

class ViewController: NSViewController{
    
    var utils = Utils()
    var audioPlayer:AVAudioPlayer!
    
    var counter = 0
    var normalTimer = NSTimer()
    var overTimer = NSTimer()
    
    var limitTime = "05:00"//05:00
    var overTimerCount = "00:00"
    
    var timer_flg = false
    var start_button_tapped = false
    var over_timer_flg = false
    var watch_flg = false
    
    @IBOutlet var semicolon: NSImageView!
    @IBOutlet var first_minute: NSImageView!
    @IBOutlet var second_minute: NSImageView!
    @IBOutlet var first_second: NSImageView!
    @IBOutlet var second_second: NSImageView!
    @IBOutlet var windowOverView: NSImageView!
    
    //ウォッチボタン
    @IBOutlet var watchButton: NSButton!    
    @IBAction func WatchButtonTapped(sender: AnyObject) {
        if watch_flg == false{
            limitTime = "05:00"
            overTimerCount = "00:00"
            startButton2.image = NSImage(named:"nixieStartButton")
            
            normalTimer.invalidate()
            overTimer.invalidate()
            
            timer_flg = false
            start_button_tapped = false
            over_timer_flg = false
            watch_flg = true
            
            startButton2.enabled = false
            resetButton2.enabled = false
            
            normalTimer = NSTimer.scheduledTimerWithTimeInterval(
                1.0,
                target: self,
                selector: #selector(ViewController.nowTime),
                userInfo: nil,
                repeats: true)
            
        }else{
            watch_flg = false
            startButton2.enabled = true
            resetButton2.enabled = true
            
            //強制リセット
            limitTime = "05:00"//05:00
            overTimerCount = "00:00"
            startButton2.image = NSImage(named:"nixieStartButton")
            
            normalTimer.invalidate()
            overTimer.invalidate()
            
            timer_flg = false
            start_button_tapped = false
            over_timer_flg = false
            
            time.stringValue = limitTime;
            self.changeNixieImage()
        }
        
    }
    //スタートボタン
    @IBOutlet var startButton2: NSButton!
    @IBAction func StartButtonTapped2(sender: AnyObject) {
        //スタート処理
        if timer_flg == false && start_button_tapped == false && over_timer_flg == false{
            normalTimer = NSTimer.scheduledTimerWithTimeInterval(
                1.0,
                target: self,
                selector: #selector(ViewController.update),
                userInfo: nil,
                repeats: true)
            
            startButton2.image = NSImage(named:"nixieStopButton")
            start_button_tapped = true
            
        }else{//ストップ処理
            if over_timer_flg == true {//タイムオーバーストップ処理
                
                print(timer_flg)
                print(start_button_tapped)
                print(over_timer_flg)
                
                overTimer.invalidate()
                over_timer_flg = false
                startButton2.image = NSImage(named:"nixieStartButton")
                
                if audioPlayer.play() == true{
                    audioPlayer.stop()
                }
                
            }else{//制限時間ストップ処理
                if over_timer_flg == false && timer_flg == false {
                    normalTimer.invalidate()
                    start_button_tapped = false
                    startButton2.image = NSImage(named:"nixieStartButton")
                }
            }
        }
    }

    //リセットボタン
    @IBOutlet var resetButton2: NSButton!
    @IBAction func ResetButtonTapped2(sender: AnyObject) {
        //強制リセット
        limitTime = "05:00"//05:00
        overTimerCount = "00:00"
        startButton2.image = NSImage(named:"nixieStartButton")
        
        normalTimer.invalidate()
        overTimer.invalidate()
        
        timer_flg = false
        start_button_tapped = false
        over_timer_flg = false
        
        time.stringValue = limitTime;
        
        windowOverView.wantsLayer = true
        windowOverView.layer?.backgroundColor = NSColor(calibratedRed: 0.0, green: 0.0, blue: 0.0, alpha: 0.0).CGColor
        
        if audioPlayer.play() == true{
            audioPlayer.stop()
        }
        self.changeNixieImage()
    }
    
    //タイマー更新
    func update(){
        //制限時間描画処理
        let df:NSDateFormatter = NSDateFormatter();
        df.dateFormat = "mm:ss"
        let dt:NSDate = df.dateFromString(limitTime)!
        let dt02 = NSDate(timeInterval: -1.0, sinceDate: dt)
        limitTime = df.stringFromDate(dt02)
        let strdt02 = df.stringFromDate(dt02)
        time.stringValue = strdt02;
        print(strdt02)
        
        //文字画像切り替えメソッド呼び出し
        self.changeNixieImage()
        
        //制限時間判定
        if self.time.stringValue == "00:00"{
            // タイマーの停止
            normalTimer.invalidate()
            print("end count")
            startButton2.image = NSImage(named:"nixieStopButton")
            
            timer_flg = true
            start_button_tapped = false
            over_timer_flg = true
            
            //画面赤かぶせ
            windowOverView.wantsLayer = true
            windowOverView.layer?.backgroundColor = NSColor(calibratedRed: 1.0, green: 0.0, blue: 0.0, alpha: 0.15).CGColor
            
            //タイムオーバータイマー開始
            overTimer = NSTimer.scheduledTimerWithTimeInterval(
                1.0,
                target: self,
                selector: #selector(ViewController.overTimeUpdate),
                userInfo: nil,
                repeats: true)
            
            //BGM開始
            self.startBGM()
        }
    }
    
    //タイムオーバー
    func overTimeUpdate(){
        let df:NSDateFormatter = NSDateFormatter();
        df.dateFormat = "mm:ss"
        let dt:NSDate = df.dateFromString(overTimerCount)!
        let dt02 = NSDate(timeInterval: 1.0, sinceDate: dt)
        overTimerCount = df.stringFromDate(dt02)
        let strdt02 = df.stringFromDate(dt02)
        time.stringValue = strdt02;
        print(strdt02)
        
        //文字画像切り替えメソッド呼び出し
        self.changeNixieImage()
    }
    
    func startBGM(){
        audioPlayer.numberOfLoops = -1
        audioPlayer.play()
    }
    
    //タイマー画像変更
    func changeNixieImage(){
        let time_str:String = time.stringValue
        let index0 = time_str.startIndex.advancedBy(0)
        let index1 = time_str.startIndex.advancedBy(1)
        let index3 = time_str.startIndex.advancedBy(3)
        let index4 = time_str.startIndex.advancedBy(4)
        
        let first_char:Character = time_str[index0]
        let second_char:Character = time_str[index1]
        let fourth_char:Character = time_str[index3]
        let fifth_char:Character = time_str[index4]
                
        let first_image:NSImage = utils.checkNixieImage(first_char)
        first_minute.image = first_image
        let second_image:NSImage = utils.checkNixieImage(second_char)
        second_minute.image = second_image
        let fourth_image:NSImage = utils.checkNixieImage(fourth_char)
        first_second.image = fourth_image
        let fifth_image:NSImage = utils.checkNixieImage(fifth_char)
        second_second.image = fifth_image
        
        print(first_char)
        print(second_char)
        print(fourth_char)
        print(fifth_char)
        print(time_str)
        
    }
    
    //時計モード
    func nowTime(){
        let date = NSDate() // Dec 27, 2015, 7:36 PM
        let cal = NSCalendar.currentCalendar()
        let comp = cal.components(
            [NSCalendarUnit.Year, NSCalendarUnit.Month, NSCalendarUnit.Day,
                NSCalendarUnit.Hour, NSCalendarUnit.Minute, NSCalendarUnit.Second],
            fromDate: date)
        
        print(comp.hour)
        print(comp.minute)
        
        let hour:String = utils.addZero(String(comp.hour), timeNuber: comp.hour)
        let minute:String = utils.addZero(String(comp.minute), timeNuber: comp.minute)
        
        let hour_index0 = hour.startIndex.advancedBy(0)
        let hour_index1 = hour.startIndex.advancedBy(1)
        let minute_index0 = minute.startIndex.advancedBy(0)
        let minute_index1 = minute.startIndex.advancedBy(1)
        
        let first_char:Character = hour[hour_index0]
        let second_char:Character = hour[hour_index1]
        let fourth_char:Character = minute[minute_index0]
        let fifth_char:Character = minute[minute_index1]
        
        let first_image:NSImage = utils.checkNixieImage(first_char)
        first_minute.image = first_image
        let second_image:NSImage = utils.checkNixieImage(second_char)
        second_minute.image = second_image
        let fourth_image:NSImage = utils.checkNixieImage(fourth_char)
        first_second.image = fourth_image
        let fifth_image:NSImage = utils.checkNixieImage(fifth_char)
        second_second.image = fifth_image
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.wantsLayer = true
        watchButton.image = NSImage(named:"watch_image3")
        watchButton.bordered = false
        watchButton.sizeToFit()
        
        let sound_data = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("bgm", ofType:"mp3")!)
        print(sound_data)
        audioPlayer = try? AVAudioPlayer(contentsOfURL: sound_data)
        
        startButton2.image = NSImage(named:"nixieStartButton")
        startButton2.bordered = false
        startButton2.sizeToFit()
        
        resetButton2.image = NSImage(named:"nixieResetButton")
        resetButton2.bordered = false
        resetButton2.sizeToFit()
        
        let image1:NSImage = NSImage(named: "nixie_0")!
        print(image1)
    }
    
    override var representedObject: AnyObject? {
        didSet {        }
    }
    
    override func awakeFromNib() {
        if self.view.layer != nil {
            let color : CGColorRef = CGColorCreateGenericRGB(0, 0, 0, 1.0)
            self.view.layer?.backgroundColor = color
        }
        
    }
    
}


Utils.swift

import Cocoa

class Utils: NSObject {
    func checkNixieImage(number_str:Character) -> NSImage{
        //var path_file_name = "";
        
        let image0:NSImage = NSImage(named: "nixie_0")!
        
        if number_str == "0"{
            print(image0)
            return image0
            
        }else if number_str == "1"{
            let image1:NSImage = NSImage(named: "nixie_1")!
            print(image1)
            return image1
            
        }else if number_str == "2"{
            let image2:NSImage = NSImage(named: "nixie_2")!
            print(image2)
            return image2
            
        }else if number_str == "3"{
            let image3:NSImage = NSImage(named: "nixie_3")!
            print(image3)
            return image3
            
        }else if number_str == "4"{
            let image4:NSImage = NSImage(named: "nixie_4")!
            print(image4)
            return image4
            
        }else if number_str == "5"{
            let image5:NSImage = NSImage(named: "nixie_5")!
            print(image5)
            return image5
            
        }else if number_str == "6"{
            let image6:NSImage = NSImage(named: "nixie_6")!
            print(image6)
            return image6
            
        }else if number_str == "7"{
            let image7:NSImage = NSImage(named: "nixie_7")!
            print(image7)
            return image7
            
        }else if number_str == "8"{
            let image8:NSImage = NSImage(named: "nixie_8")!
            print(image8)
            return image8
            
        }else if number_str == "9"{
            let image9:NSImage = NSImage(named: "nixie_9")!
            print(image9)
            return image9
        }

        return image0
    }
    
    func addZero(timeString:String,timeNuber:Int)->String{
        if timeString.characters.count==1 {
            return "0\(timeNuber)"
        }else{
            return "\(timeNuber)"
        }
    }
    
}

欲がでしまい、中央のボタンを押すと時計にもなるようになっています。

今回、一番悩んだのがmacアプリでのリソースの配置です。普段わたしはiPhoneアプリで画像などのリソースを参照するとき、パスを指定して参照するのですが、
macアプリでパス指定のリソース参照がうまくできず、うんうん悩んでいた結果、xcassetsを使うといいことに気づきました。

まず、xcassetsファイルに参照したい画像や音データを入れておきます。

コードでは、上記の一部抜粋ですが、

        let sound_data = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("bgm", ofType:"mp3")!)
        print(sound_data)
        audioPlayer = try? AVAudioPlayer(contentsOfURL: sound_data)

こちらのコードでリソースデータを取得することができました。

結果的にできたのが一番上の画像のアプリなのですが、そこそこいいできなのではと思っています。

改善点もちらほらあったので機会があればもっと良くしようかなと思います。