macアプリにチャレンジしてみた
swiftの勉強進めてます。
if文に括弧つけなくてよかったり、セミコロンを最後につけなくてよかったり
swiftに慣れすぎると別の言語を書く際に文法がごちゃごちゃになって大変なことになりそうです。
でも最近は簡略化してて逆に見やすいと感じるようにもなってきました。とりあえず勉強進めて実用レベルまで持って行きたいところです。
今回の投稿はタイマーを作る機会があったのでiPhoneアプリではなく、
macアプリを自分なりに作ってみたのでそれに関しての備忘録です。
macアプリ、開発者登録してないと起動時に怒られますね・・・。
でもリリースするわけではないのでとりあえずスルーしました。
配布もごく一部に限定的に行うのでシステム環境設定のセキュリティとプライバシーからダウンロードしたアプリケーションの実行許可をすべてのアプリケーションを許可にしてもらったりと、使ってもらう側にも設定してもらう必要ありでした。
では、簡単に本題に今回作ったのはニキシー管風味のタイマーアプリです。
完成したものはこれ
ビジュアルは割とよくできたかなと思ってます。
では簡単に手順を説明します。なにぶんmacアプリを作ったのが初めてなのでいろいろ間違ったところもあると思いますが、ご容赦ください。
というか、そもそもmacアプリに関する資料が少ない・・・検索をかけてもiPhoneアプリの記事ばかりでやりたいことと微妙に食い違いがあってアプリ作成には難航しました。
はい、話進めます。
まず最初にやったのはXcodeの新規Project作成でOS X>Cocoa 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)
こちらのコードでリソースデータを取得することができました。
結果的にできたのが一番上の画像のアプリなのですが、そこそこいいできなのではと思っています。
改善点もちらほらあったので機会があればもっと良くしようかなと思います。