Pengatur waktu IOS

Bayangkan Anda sedang mengerjakan suatu aplikasi di mana Anda perlu melakukan beberapa tindakan secara berkala. Inilah yang digunakan Swift untuk kelas Timer .

Timer digunakan untuk merencanakan tindakan dalam aplikasi. Ini bisa berupa tindakan satu kali atau prosedur berulang.

Dalam panduan ini, Anda akan mempelajari cara timer bekerja di iOS, bagaimana timer dapat memengaruhi responsif UI, cara mengoptimalkan konsumsi baterai saat menggunakan timer, dan cara menggunakan CADisplayLink untuk animasi.

Sebagai situs uji, kami akan menggunakan aplikasi - penjadwal tugas primitif.

Memulai


Unduh proyek sumber. Buka di Xcode, lihat strukturnya, kompilasi dan eksekusi. Anda akan melihat penjadwal tugas yang paling sederhana:



Tambahkan tugas baru ke sana. Ketuk ikon +, masukkan nama tugas, ketuk Ok.

Tugas yang ditambahkan memiliki stempel waktu. Tugas baru yang baru saja Anda buat ditandai dengan nol detik. Seperti yang Anda lihat, nilai ini tidak meningkat.

Setiap tugas dapat ditandai sebagai selesai. Ketuk tugas tersebut. Nama tugas akan dicoret dan akan ditandai sebagai selesai.

Buat timer pertama kami


Mari kita buat timer utama dari aplikasi kita. Kelas Timer , juga dikenal sebagai NSTimer, adalah cara yang nyaman untuk menjadwalkan tindakan untuk momen tertentu, baik tunggal maupun berkala.

Buka TaskListViewController.swift dan tambahkan variabel ini ke TaskListViewController :

var timer: Timer? 

Kemudian tambahkan ekstensi di sana:

 // MARK: - Timer extension TaskListViewController { } 

Dan rekatkan kode ini ke dalam ekstensi:

 @objc func updateTimer() { // 1 guard let visibleRowsIndexPaths = tableView.indexPathsForVisibleRows else { return } for indexPath in visibleRowsIndexPaths { // 2 if let cell = tableView.cellForRow(at: indexPath) as? TaskTableViewCell { cell.updateTime() } } } 

Dalam metode ini, kami:

  1. Periksa apakah ada baris yang terlihat di tabel tugas.
  2. Panggil updateTime untuk setiap sel yang terlihat. Metode ini memperbarui timestamp di sel (lihat TaskTableViewCell.swift ).

Kemudian tambahkan kode ini ke ekstensi:

 func createTimer() { // 1 if timer == nil { // 2 timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) } } 

Di sini kita:

  1. Periksa apakah timer berisi instance kelas Timer .
  2. Jika tidak, buat pengatur waktu yang memanggil updateTimer () setiap detik.

Maka kita perlu membuat timer segera setelah pengguna menambahkan tugas pertama. Tambahkan createTimer () di bagian paling awal metode presentAlertController (_ :) .

Luncurkan aplikasi dan buat beberapa tugas baru. Anda akan melihat bahwa cap waktu untuk setiap tugas berubah setiap detik.



Tambahkan toleransi waktu


Peningkatan jumlah pengatur waktu menghasilkan respons UI yang lebih buruk dan konsumsi baterai yang lebih banyak. Setiap timer mencoba mengeksekusi tepat pada waktu yang ditentukan, karena secara default toleransinya adalah nol.

Menambahkan toleransi waktu adalah cara mudah untuk mengurangi konsumsi energi. Ini memungkinkan sistem untuk melakukan tindakan pengatur waktu antara waktu yang ditetapkan dan waktu yang ditetapkan ditambah waktu toleransi - tetapi tidak pernah sebelum interval yang ditetapkan.

Untuk penghitung waktu yang berjalan hanya sekali, nilai toleransi diabaikan.

Dalam metode createTimer () , segera setelah penetapan waktu, tambahkan baris ini:

 timer?.tolerance = 0.1 

Luncurkan aplikasi. Dalam kasus khusus ini, efeknya tidak akan terlihat jelas (kami hanya memiliki satu pengatur waktu), namun, dalam situasi nyata dari beberapa pengatur waktu, pengguna Anda akan mendapatkan antarmuka yang lebih responsif dan aplikasi akan lebih hemat energi.



Pengatur waktu di latar belakang


Menariknya, apa yang terjadi pada penghitung waktu saat aplikasi masuk ke latar belakang? Untuk mengatasinya, tambahkan kode ini di bagian paling awal metode updateTimer () :

 if let fireDateDescription = timer?.fireDate.description { print(fireDateDescription) } 

Ini akan memungkinkan kita untuk melacak peristiwa penghitung waktu di konsol.

Jalankan aplikasi, tambahkan tugas. Sekarang tekan tombol Rumah di perangkat Anda, dan kemudian kembali ke aplikasi kami.

Di konsol, Anda akan melihat sesuatu seperti ini:



Seperti yang Anda lihat, ketika aplikasi masuk ke latar belakang, iOS menjeda semua penghitung waktu aplikasi yang berjalan. Ketika aplikasi menjadi aktif, iOS melanjutkan penghitung waktu.

Memahami Run Loops


Run loop adalah loop acara yang menjadwalkan pekerjaan dan menangani acara yang masuk. Siklus membuat utas sibuk saat sedang berjalan dan menempatkannya dalam kondisi "tidur" ketika tidak ada pekerjaan untuk itu.

Setiap kali Anda meluncurkan aplikasi, sistem membuat utas utama aplikasi, setiap utas memiliki loop eksekusi yang dibuat secara otomatis untuknya.

Tetapi mengapa semua informasi ini penting bagi Anda sekarang? Sekarang setiap timer dimulai di utas utama dan bergabung dengan loop eksekusi. Anda mungkin menyadari bahwa utas utama terlibat dalam rendering antarmuka pengguna, pemrosesan sentuhan, dan sebagainya. Jika utas utama sibuk dengan sesuatu, antarmuka aplikasi Anda mungkin menjadi "tidak responsif" (hang).

Apakah Anda memperhatikan bahwa cap waktu di sel tidak diperbarui saat Anda menyeret tampilan tabel?



Anda dapat mengatasi masalah ini dengan memberi tahu siklus lari untuk memulai penghitung waktu dalam mode yang berbeda.

Memahami Mode Jalankan Siklus


Mode siklus eksekusi adalah serangkaian sumber input, seperti menyentuh layar atau mengklik mouse, yang dapat diatur untuk memantau dan satu set "pengamat" yang menerima pemberitahuan.

Ada tiga mode runtime di iOS:

default : sumber input yang bukan NSConnectionObjects diproses.
umum : satu set siklus input sedang diproses, di mana Anda dapat menentukan satu set sumber input, timer, "pengamat".
pelacakan : UI aplikasi sedang diproses.

Untuk aplikasi kita, mode yang paling cocok adalah umum . Untuk menggunakannya, ganti konten metode createTimer () dengan yang berikut:

 if timer == nil { let timer = Timer(timeInterval: 1.0, target: self, selector: #selector(updateTimer), userInfo: nil, repeats: true) RunLoop.current.add(timer, forMode: .common) timer.tolerance = 0.1 self.timer = timer } 

Perbedaan utama dari kode sebelumnya adalah bahwa sebelum menetapkan timer ke TaskListViewController, kami menambahkan timer ini ke run loop dalam mode umum .

Kompilasi dan jalankan aplikasi.



Sekarang cap waktu sel diperbarui bahkan jika tabel digulir.

Tambahkan animasi untuk menyelesaikan semua tugas


Sekarang kami menambahkan animasi selamat untuk pengguna untuk menyelesaikan semua tugas - bola akan naik dari bagian bawah layar ke bagian paling atas.

Tambahkan variabel-variabel ini di awal TaskListViewController:

 // 1 var animationTimer: Timer? // 2 var startTime: TimeInterval?, endTime: TimeInterval? // 3 let animationDuration = 3.0 // 4 var height: CGFloat = 0 

Tujuan dari variabel-variabel ini adalah:

  1. penyimpanan pewaktu animasi.
  2. penyimpanan waktu awal dan akhir animasi.
  3. durasi animasi.
  4. tinggi animasi.

Sekarang tambahkan ekstensi TaskListViewController berikut di akhir file TaskListViewController.swift :

 // MARK: - Animation extension TaskListViewController { func showCongratulationAnimation() { // 1 height = UIScreen.main.bounds.height + balloon.frame.size.height // 2 balloon.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: height + balloon.frame.size.height / 2) balloon.isHidden = false // 3 startTime = Date().timeIntervalSince1970 endTime = animationDuration + startTime! // 4 animationTimer = Timer.scheduledTimer(withTimeInterval: 1 / 60, repeats: true) { timer in // TODO: Animation here } } } 

Di sini kita lakukan hal berikut:

  • menghitung ketinggian animasi, mendapatkan ketinggian layar perangkat
  • pusatkan bola di luar layar dan atur visibilitasnya
  • menetapkan waktu mulai dan akhir animasi
  • kami memulai timer animasi dan memperbarui animasi 60 kali per detik

Sekarang kita perlu membuat logika aktual untuk memperbarui animasi ucapan selamat. Tambahkan kode ini setelah showCongratulationAnimation () :

 func updateAnimation() { // 1 guard let endTime = endTime, let startTime = startTime else { return } // 2 let now = Date().timeIntervalSince1970 // 3 if now >= endTime { animationTimer?.invalidate() balloon.isHidden = true } // 4 let percentage = (now - startTime) * 100 / animationDuration let y = height - ((height + balloon.frame.height / 2) / 100 * CGFloat(percentage)) // 5 balloon.center = CGPoint(x: balloon.center.x + CGFloat.random(in: -0.5...0.5), y: y) } 

Apa yang kita lakukan:

  1. periksa endTime dan startTime ditugaskan
  2. menghemat waktu saat ini dalam konstan
  3. kami memastikan bahwa waktu terakhir belum tiba. Jika sudah tiba, perbarui timer dan sembunyikan bola kami
  4. hitung koordinat-y bola yang baru
  5. posisi horizontal bola dihitung relatif terhadap posisi sebelumnya

Sekarang ganti // TODO: Animasi di sini di showCongratulationAnimation () dengan kode ini:

 self.updateAnimation() 

Sekarang updateAnimation () dipanggil setiap kali peristiwa timer terjadi.

Hore, kami baru saja membuat animasi. Namun, ketika aplikasi dimulai, tidak ada yang baru terjadi ...

Menampilkan animasi


Seperti yang Anda duga, tidak ada yang bisa "meluncurkan" animasi baru kami. Untuk melakukan ini, kita perlu metode lain. Tambahkan kode ini ke ekstensi animasi TaskListViewController:

 func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { showCongratulationAnimation() } } 

Kami akan memanggil metode ini setiap kali pengguna menandai tugas selesai, ia memeriksa apakah semua tugas selesai. Jika demikian, ia akan memanggil showCongratulationAnimation () .

Sebagai kesimpulan, tambahkan panggilan ke metode ini di akhir tableView (_: didSelectRowAt :) :

 showCongratulationsIfNeeded() 

Luncurkan aplikasi, buat beberapa tugas, tandai sebagai selesai - dan Anda akan melihat animasi kami!



Kami menghentikan timer


Jika Anda melihat konsol, Anda akan melihat bahwa, meskipun pengguna menandai semua tugas selesai, timer tetap berfungsi. Ini sama sekali tidak berguna, jadi masuk akal untuk menghentikan timer ketika tidak diperlukan.

Pertama, buat metode baru untuk menghentikan timer:

 func cancelTimer() { timer?.invalidate() timer = nil } 

Ini akan memperbarui timer dan meresetnya ke nol sehingga kita dapat membuatnya dengan benar lagi nanti. invalidate () adalah satu-satunya cara untuk menghapus Timer dari run loop. Run loop akan menghapus referensi timer kuat baik segera setelah panggilan tidak valid () atau sedikit kemudian.

Sekarang ganti metode showCongratulationsIfNeeded () sebagai berikut:

 func showCongratulationsIfNeeded() { if taskList.filter({ !$0.completed }).count == 0 { cancelTimer() showCongratulationAnimation() } else { createTimer() } } 

Sekarang, jika pengguna menyelesaikan semua tugas, aplikasi pertama-tama akan mengatur ulang timer dan kemudian menunjukkan animasi, jika tidak maka akan mencoba untuk membuat timer baru jika belum ada di sana.

Luncurkan aplikasi.



Sekarang penghitung waktu berhenti dan memulai kembali sebagaimana mestinya.

CADisplayLink untuk animasi yang halus


Timer bukanlah pilihan ideal untuk mengendalikan animasi. Anda mungkin memperhatikan beberapa frame melewatkan animasi, terutama jika Anda menjalankan aplikasi dalam simulator.

Kami mengatur timer ke 60Hz. Dengan demikian, pengatur waktu memperbarui animasi setiap 16 ms. Pertimbangkan situasinya lebih dekat:



Saat menggunakan Timer, kami tidak tahu kapan tepatnya tindakan dimulai. Ini bisa terjadi baik di awal atau di akhir bingkai. Katakanlah timer berjalan di tengah setiap frame (titik-titik biru pada gambar). Satu-satunya hal yang kita tahu dengan pasti adalah bahwa panggilan akan setiap 16 ms.

Sekarang kita hanya memiliki 8 ms untuk menjalankan animasi, dan ini mungkin tidak cukup untuk animasi kita. Mari kita lihat frame kedua pada gambar. Bingkai kedua tidak dapat diselesaikan dalam waktu yang ditentukan, sehingga aplikasi akan mengatur ulang bingkai animasi kedua.

CADisplayLink akan membantu kami


CADisplayLink dipanggil sekali per bingkai dan mencoba menyinkronkan frame animasi nyata sebanyak mungkin. Sekarang Anda akan memiliki semua 16 ms yang Anda inginkan dan iOS tidak akan menjatuhkan satu frame pun.

Untuk menggunakan CADisplayLink , Anda perlu mengganti animationTimer dengan tipe baru.

Ganti kode ini

 var animationTimer: Timer? 

yang ini:

 var displayLink: CADisplayLink? 

Anda telah mengganti Timer dengan CADisplayLink . CADisplayLink adalah tampilan penghitung waktu yang terkait dengan pemindaian vertikal tampilan. Ini berarti bahwa GPU perangkat akan berhenti sampai layar dapat melanjutkan untuk memproses perintah GPU. Dengan cara ini kita mendapatkan animasi yang halus.

Ganti kode ini

 var startTime: TimeInterval?, endTime: TimeInterval? 

yang ini:

 var startTime: CFTimeInterval?, endTime: CFTimeInterval? 


Anda mengganti TimeInterval dengan CFTimeInterval , yang diperlukan untuk bekerja dengan CADisplayLink.

Ganti teks metode showCongratulationAnimation () dengan ini:

 func showCongratulationAnimation() { // 1 height = UIScreen.main.bounds.height + balloon.frame.size.height balloon.center = CGPoint(x: UIScreen.main.bounds.width / 2, y: height + balloon.frame.size.height / 2) balloon.isHidden = false // 2 startTime = CACurrentMediaTime() endTime = animationDuration + startTime! // 3 displayLink = CADisplayLink(target: self, selector: #selector(updateAnimation)) displayLink?.add(to: RunLoop.main, forMode: .common) } 

Apa yang kita lakukan di sini:

  1. Atur ketinggian animasi, koordinat bola dan visibilitas - hampir sama dengan yang mereka lakukan sebelumnya.
  2. Inisialisasi startTime dengan CACurrentMediaTime () (bukan Date ()).
  3. Kami membuat instance dari kelas CADisplayLink dan menambahkannya ke run loop dalam mode umum .

Sekarang ganti updateAnimation () dengan kode berikut:

 // 1 @objc func updateAnimation() { guard let endTime = endTime, let startTime = startTime else { return } // 2 let now = CACurrentMediaTime() if now >= endTime { // 3 displayLink?.isPaused = true displayLink?.invalidate() balloon.isHidden = true } let percentage = (now - startTime) * 100 / animationDuration let y = height - ((height + balloon.frame.height / 2) / 100 * CGFloat(percentage)) balloon.center = CGPoint(x: balloon.center.x + CGFloat.random(in: -0.5...0.5), y: y) } 

  1. Tambahkan objc ke tanda tangan metode (untuk CADisplayLink, parameter pemilih memerlukan tanda tangan seperti itu).
  2. Ganti inisialisasi dengan Date () untuk menginisialisasi tanggal CoreAnimation .
  3. Ganti panggilan animationTimer.invalidate () dengan jeda dari CADisplayLink dan batal. Ini juga akan menghapus CADisplayLink dari run loop.

Luncurkan aplikasi!


Hebat! Kami berhasil mengganti animasi berbasis Timer dengan CADisplayLink yang lebih sesuai - dan mendapatkan animasi lebih lancar, tanpa menyentak.

Kesimpulan


Dalam panduan ini, Anda mengetahui bagaimana kelas Timer bekerja di iOS, apa siklus eksekusi itu dan bagaimana hal itu dapat membuat aplikasi Anda lebih responsif dalam hal antarmuka, dan bagaimana menggunakan CADisplayLink daripada Timer untuk animasi yang lancar.

Source: https://habr.com/ru/post/id450172/


All Articles