Membuat aplikasi Mac OS pertama

Di postingan sebelumnya gue menulis tentang ketertarikan gue dalam mempelajari bahasa pemrograman Swift, yang akan diimplementasikan ke pembuatan aplikasi Mac OS/iOS.

Dulu waktu gue belajar React, gue inget banget contoh awal gue dalam implementasi React adalah membuat real-time watch/clock. Jadi disitu gue membuat sebuah halaman website yang menampilkan "Jam:Menit:Detik" sesuai dengan timezone laptop lo, dan akan meng-update nya setiap detik.

Lalu gue mempelajari lah Component Lifecycle di React, dengan Thinking in React nya, gue menginisialisasi label "waktu" dengan "Loading...", lalu ketika component sudah berhasil di mount via componentDidMount() disitu gue buat interval yang mana setiap detik memanggil fungsi updateTimer(), yang hanya meng-set state di React via setState.

Sekarang mau gue coba di Mac OS. Desain aplikasi nya simple. Seperti ini:

Disitu gue enggak pakai 'Loading...' karena enggak deskriptif, haha. Anggep aja itu splashscreen lah.

Logic nya, ketika si aplikasi berhasil diload, maka sama seperti di React tersebut: Kita ubah tulisan Real Time Watch dengan "Jam:Menit:Detik". Silahkan hubungkan label "Real Time Watch" tersebut dari Storyboard ke ViewController.swift, lalu buat Outlets dengan nama clockLabel (terserah deh namain nya apa). Maka si Xcode akan meng-generate code seperti berikut:

@IBOutlet weak var clockLabel: NSTextField!

ya @IBOutlet adalah directive untuk Interface Builder Outlet (outlet untuk mereferensikan sebuah 'component' yang ada di storyboard), untuk menghubungkan sesuatu yang ada di storyboard. Jadi, untuk bisa mengakses property dari label tersebut (clockLabel) kita harus menghubungkan nya ke ViewController via @IBOutlet.

Lalu, ketika view sudah berhasil di load, maka kita nilai ubah label tersebut menjadi waktu pada detik ini. Disini kita akan mengubah nya via viewDidLoad() (yaa seperti componentDidMount)

override func viewDidLoad () {
let date = Date()
let calendar = Calendar.current
let hours = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
clockLabel.stringValue = "\(hours):\(minutes):\(seconds)"
}

Apa itu, riz?

Sama seperti di JavaScript, untuk bisa menampilkan 'waktu sekarang', kita harus membuat objek dari class Date, beda nya di Swift enggak pakai kata kunci new haha

Lalu... ketika di JavaScript untuk mendapatkan jam sekarang tinggal panggil <Date>.getHours() , karena ini Swift, dan Date di Swift merupakan sebuah structure yang independent (tidak terikat dengan calendar atau timezone), jadi nilai dari date tersebut adalah "2018-04-07 21:07:22 +0000" (07 April 2018, jam 21:07:22 UTC +0000). Dan di timezone kita ini Indonesia adalah UTC +7 (harusnya tanggal 08 April 2018, jam 04:07:22. Beda 7 jam). 

Sekarang, gimana cara nya buat ngambil data Jam:Menit:Detik tersebut? Untung dokumentasi Apple ini asik. Tentang Swift, Objective-C, dll ada di Xcode. Disitu dikasih tau kalau Use date values in conjunction with DateFormatter instances to create localized representations of dates and times and with Calendarinstances to perform calendar arithmetic. So, I need Calendar instance! Calendar.current berisi gregorian, yang mana jenis kalender yang ada di laptop gue ini adalah gregorian.
 
Sekarang kita ambil data hour tersebut dari instance Calendar. Btw, si Calendar ini bertipe data Struct. Di dokumentasi nya, untuk mengambil data-data seperti jam, menit, detik, kita perlu memanggil fungsi Component dari instance Calendar, dengan parameter pertama 'data' apa yang ingin diambil, dan parameter kedua nya 'dari mana si data tersebut diambil'. Disini kita pengen data nya dari instance Date.
 
Parameter pertama tersebut (component yang nilainya ingin diambil) bertipe enum, jadi ketika kita mempass .hour, si enum component ini akan mencocokkan via case , dan akan mengembalikkan nilai bertipe Integer (<CalendarInstance>.Component(.<whatComponent>, from: <DateInstance>) -> Int).
 
Oke udah sedikit ngerti lah ya, sekarang kita bisa menampilkan data Jam:Menit:Detik berdasarkan timezone kita, kalau enggak percaya apakah itu dari timezone kita, coba aja akses Calendar.timeZone), apakah bernilai "Asia/Jakarta (current)" ?
 
Tau dari mana riz untuk ambil nilai  timeZone ternyata dari instance Calendar langsung, bukan dari enum Component (soalnya di enum component juga ada)?
 
Yaa coba-coba dong wkwkw. Pertama pas coba, salah. katanya si timeZone kampret ini enggak bisa diambil via NSCalendarUnitTimeZone. Terus baca dokumentasi nya, dan nemu deh. thanks Apple.
 
Lanjut.
 
Lalu kita ubah nilai dari si label tersebut menjadi: \(hours):\(minutes):\(seconds) . \(variable) kalau di JavaScript seperti template literal gitu lah. Dan ya, ini ribet banget dah klo ditulis (sering pahili, kadang malah inget nulis regex jadi (\namaVariable\). sekedar curhat aja.
 
kita ubah label tersebt langsung dengan meng-assign nilai `stringValue` nya. mantap jiwa. sedikit scary, karena di React biasanya pakai setState untuk mengubah state yang ada di app kita. Tapi jadi biasa aja setelah menggunakan Vue (I love observable).
 
Sekarang, mari kita buat si nilai ini berubah setiap detik. Gue pun googling How to setInterval in Swift, dan mendapatkan jawaban nya disini.
 
// updated to Swift 4.0

func setInterval(interval: TimeInterval, block: @escaping ()-> Void) -> Timer {
return Timer.scheduledTimer(timeInterval: interval, target: BlockOperation(block: block), selector: #selector(Operation.main), userInfo: nil, repeats: true)
}
 
Mari kita coba panggil setInterval ini di viewDidLoad. Lalu kita buat function yang akan dipanggil selama n detik di setInterval ini. Namain function nya apa ya, changeTheValue aja deh wkwkwk.
 
func changeTheValue () -> Void {
let date = Date()
let calendar = Calendar.current
let hours = calendar.component(.hour, from: date)
let minutes = calendar.component(.minute, from: date)
let seconds = calendar.component(.second, from: date)
let updatedLabelValue = "\(hours):\(minutes):\(seconds)"
clockLabel.stringValue = updatedLabelValue
}

 

Dan taraaa...

Disitu gue kasih atribut @discardableResult untuk menghilangkan warning di viewDidLoad karena hasil dari fungsi setInterval enggak dipake. Laah kan kita cuma buat update UI nya aja ya. Swift ini emang mantep dalam memory management, dengan menggunakan@discardableResult, itu berarti ngasih tau si compiler biar yaudah lah fungsi ini tuh meskipun result nya enggak dipake, tapi ngelakuin sesuatu da. Yaa daripada ngeassign _ di setInterval (looks little weird menurut gue mah)

Sekarang mari kita buat enhancement. Gue pengen mengubah si window ini menjadi nilai dari si waktu tersebut. So let's make it

view.window?.title = "Sekarang jam: \(updatedLabelValue)"

Maka akan berubah~

view merupakan instance dari NSView. Lalu kita mengakses instance property dari view bernama window. Ada tanda tanya tersebut ?, berarti nilai default dari object window adalah nil. Jika tidak nil (window sudah terload), maka kita akses  property title, dan ubah nilai nya.

Di postingan selanjut nya gue mau ngebahas tentang Lifecycle. Disini gue masih noob banget, masa close application manggil exit(0) :(

Source Code

Demo Apps