はじめに
更新が遅くなってしまいましたが、引きつづきPythonistaのUI実装についてです!
前回までの記事に引き続いて、今回は画面遷移を実装しようと思います。
いやいやまだPythonista全然触ったこともないんだけどって方は以下の記事も読んでみていただけると幸いです。
(ストーリー性はないので、前提知識さえあれば下記記事読まなくてもこの記事は理解できます)
[過去記事]
Pythonista3のUI実装で遊ぶ[超超入門]
PythonistaのUI実装で遊ぶ[画面要素]
PythonistaのUI実装で遊ぶ[Actionの実装]
画面遷移
単一の画面で完結しているアプリはほとんどありませんよね。
PythonistaでUIを実装していると、画面遷移したくて震えがとまらなくなってしまうことがあるのではないでしょうか。わかります。わかりますとも。
というわけで今回は以下のように動く簡単なリマインダアプリを作ってみたいと思います。朝弱いけど、自分で作ったアラームならばなんか起きれる気がすんだよなーって方、必見です!
青い四角が画面です。アラーム一覧画面が最初に開いて、時刻とサウンドを選択するとアラームが設定され、最初の一覧に戻るイメージですね。
画面を作る
まず画面をぜんぶ用意しちゃいましょう。
このへん画面作って処理作ってって作り方の順番はいろいろあると思いますが、今回はそんな複雑な画面でもないですし、それぞれの画面作ってから遷移の処理入れます。
アラーム一覧画面
Pythonista の「Script with UI」でタイトルをalermlist
とすると、alarmlist.py と alarmlist.pyui が作成されます。pyuiの方を開いて以下のように要素を作成します。
画面サイズは適当に500*500ですが、やったことは以下です。
①Custom View
で画面を覆う
②Table View
を追加(デフォルト値のRow 1
とかは消しときます)
③ Button
を追加しTitleをNew Alarm
に変更
New Alarm
押して作ったアラームがここに一覧表示されるイメージですね。
今回は何もしていませんが、基本的には背景色変えたりとかすると思うのでCustom View
は入れとけばよいかと思います。
時刻設定画面
alarmlist
と同様にソースファイルdatepick
を作成します。
datepick.pyui
を以下のように作成します。
やったことは以下です。
①Custom View
で画面を覆う
②Date Picker
を追加し、Modeを「Date and Time」に変更
③ Button
を追加しTitleをOK
に変更
日付を選択してOK
押したら次のサウンド選択にいく予定の画面です。
サウンド選択画面
soundselect
を作成して、pyuiを以下のように作成します。
やったことはalarmlist.pyui
と一緒です。
ButtonのタイトルだけOK
にしてあります。
各画面の処理
さてpyuiができたのでこんどは処理を実装していきます!
アラーム一覧画面から時刻設定画面への遷移
処理の実装
「アラーム一覧を表示する」という一番大事な処理を差し置いて、画面遷移処理だけを実装します。
ようするに、New Alarm
押下でdatepick
に遷移する処理です。
alermlist.py
とdatepick.py
を書きます。
まずは遷移先のdatepick.py
。画面をクラス化してやりましょう。
import ui
class Datepick(object):
def __init__(self):
self.v = ui.load_view()
def present_view(self):
self.v.present('sheet')
意図としては、alermlist.py
でDatepick
クラスを呼び出して、New Alarm
ボタンが押下されたときにDatepick.present_view
を呼び出して画面を出していきたいわけですね。
先に言っときますけどこれだと期待通りの動作にはなりません。
ではうまくいかないことがわかっているままalarmlist.py
の方へ。
import ui
from datepick import Datepick
class AlarmList(object):
def __init__(self):
self.v = ui.load_view()
self.v['button1'].action = self.tap_new_button
def tap_new_button(self, sender):
d = DatePick()
d.present_view()
a = AlarmList()
a.v.present('sheet')
AlarmListクラスの__init__
の中でload_view
して、各要素の属性を設定します。ここではNew Alarm
ボタンにActionをつけていますね。
こうすることで、ボタン押下時に処理が動きます。
ちなみにpresent()
の引数sheet
はiPadだけみたいなので、iPhoneの人はfullscreen
指定と同じ動作になります。
画面の自動リサイズで表示が変になりますが、pyuiのAuto-Resizing / Flex
でなんとかしましょう。
(画面サイズをお使いのiPhoneと同じにすると自動リサイズされなくなります)
実行
alarmlist.py
を実行すると以下のような画面が出ます。
さて、本番はここからです。ドキドキしながらNew Alarm
を押してみましょう。
やったー!時刻選択画面に遷移したぞ!
よーし1回閉じて、続きの処理を実装していきましょう。。。ん?
×を押したら時刻選択画面が閉じて、最初の画面が後ろからでてきました。思ってたんと違う!
present()
は実行すると「新しい画面を生成」するので、先の例のままでは次々と新しい画面が生成されていくだけです。
...てぇ...移してぇ...同じ画面のまま画面遷移してぇ...
NavigationViewクラスを使う
小見出しではありますがここが今回の記事でいちばん大切な箇所です。
ここまで読んでいただいてありがとうございます。
PythonistaのドキュメントにはNavigationViewクラスの説明としてだいたい以下のようなことが書いてあります。
NavigationViewは、ビューのスタック/階層を表示するためのインターフェイスを提供します。ナビゲーションビューの上部には、現在のビューの名前と戻るボタンを含むナビゲーションバーが表示されます。
操作ごとにビューを順番に表示したい場合には使ってねってことです。
まずはalarmlist.py
を以下のように書き換えます。
import ui
from datepick import Datepick
class AlarmList(object):
def __init__(self):
self.v = ui.load_view()
self.v['button1'].action = self.tap_new_button
def tap_new_button(self, sender):
d = Datepick()
# 画面を遷移させる
sender.superview.navigation_view.push_view(d.v)
a = AlarmList()
nv = ui.NavigationView(a.v) # ロードしたviewをNavigationViewに埋め込み
nv.height = 500 # nvのサイズ調整もしないと表示が崩れる
nv.width = 500
nv.name = 'Alarm'
nv.present('sheet')
tap_new_button
の引数sender
には押下したButtonが入っているので、親要素→NavigationViewと辿ってpush_view()
で時刻選択画面を表示します。
datepick.py
で定義していたpresent_view()
はせっかく実装しましたが不要です消します。
import ui
class Datepick(object):
def __init__(self):
self.v = ui.load_view()
画面自体はalarmlist.py
側で呼び出しているので、ここはload_view
するだけでよくなります。v
は外から見えるようにしましょう。
実行してNew Alarm
を押します。
遷移できました!
上にバーが追加されて、Backボタンが表示されていますね。
画面遷移がNavigationView上で行われているので、Backを押すと前の画面に戻ります。すごい!
×を押しても、前の画面が残ることなく、閉じることができます。すごい!!
時刻設定画面からサウンド選択画面への遷移
さて次は時刻設定画面からサウンド選択画面への遷移です。
さっきの遷移と同じように実装しましょう。
まずは遷移先のsoundselect.py
。
import ui
class SoundSelect(object):
def __init__(self):
self.v = ui.load_view()
次に呼び出し側のdatepick.py
です。
いま作ったSoundSelectをimportします。
import ui
from soundselect import SoundSelect
class Datepick(object):
def __init__(self):
self.v = ui.load_view()
self.v['button1'].action = self.tap_ok_button
def tap_ok_button(self, sender):
s = SoundSelect()
sender.superview.navigation_view.push_view(s.v)
実行してやると時刻選択画面からサウンド選択画面に遷移できます。
NavigationViewを使っているので、時刻を間違えたことに気付いても戻って修正することができますね!
画面間で値を受け渡す
ここで必要になってくるのが、「時刻選択画面で選択した時刻をどこかに持っておく」ということ。
せっかく時刻を選択したのにどこにも残らないのでは、指定した時間にアラームがなりません!これでは起きられません!!
Datepickerで指定された時刻を取り出して、SoundSelectに渡してやりましょう。
import ui
from soundselect import SoundSelect
class Datepick(object):
def __init__(self):
self.v = ui.load_view()
self.v['button1'].action = self.tap_ok_button
def tap_ok_button(self, sender):
# DatePickerの時刻を取得
date = sender.superview['datepicker1'].date.strftime('%y/%m/%d %H:%M')
s = SoundSelect(date)
sender.superview.navigation_view.push_view(s.v)
日付はpyuiで画面に追加したDatepickerのメンバdate
に格納されているので取り出して利用します。そのままSoundSelectのコンストラクタに渡します。
SoundSelect側は、受け取った値をコンストラクタが受け取れるようにしましょう。
import ui
import console
class SoundSelect(object):
def __init__(self, date):
self.v = ui.load_view()
self.date = date
self.v['button1'].action = self.tap_ok_button
def tap_ok_button(self, sender):
console.alert(self.date)
これで値渡しはOKです。
上のはお試しで、サウンド選択画面のOKボタンを押すとconsole.alert
が動作し、設定した時刻が画面に出るようになっています。
サウンド選択画面の処理
soundselect.pyui
のテーブルに値を設定しましょう。
今回は動的に入れたりせず固定値とします。
さらにsoundselect.py
を以下のように修正します。
import ui
import console
from datetime import datetime
import notification
class SoundSelect(object):
def __init__(self, date):
self.v = ui.load_view()
self.date = date
self.v['button1'].action = self.tap_ok_button
def tap_ok_button(self, sender):
dir = 'Media/Sounds/drums/'
sounds = ['Drums_01','Drums_02','Drums_03']
selected = self.v['tableview1'].selected_row[1]
now = datetime.now() # 現在時刻を取得
delay = self.date - now # 設定時刻との差分
notification.schedule('test notify', delay=delay.total_seconds(), sound_name=dir+sounds[selected])
Pythonistaのnotification
モジュールを利用して通知を設定します。
datetime
モジュールやnotification
モジュールの詳細は割愛します。
まぁ今回の記事はアラーム設定することが目的ではないので、簡単に済ませちゃってます。この時点でアラーム設定は可能ですが、サウンドもしょぼいのを使っているので、時間になるとドラムが1音、ぺんって鳴ります。
サウンド選択画面からアラーム一覧画面へ
さて、ここまでの実装だと、アラームを設定したところで画面になんの動きもなく、別のアラームを設定したくなったらBackボタンで戻る必要があります。
(まぁワンオペですが)ちょっとカッコ悪いですよね。
なので、サウンド選択画面でOK
を押下したら、最初のアラーム一覧画面に戻るようにします。
まずは、サウンド選択画面からアラーム一覧画面を呼び出すようにします。
import ui
import console
from datetime import datetime
import notification
class SoundSelect(object):
def __init__(self, date):
self.v = ui.load_view()
self.date = date
self.v['button1'].action = self.tap_ok_button
def tap_ok_button(self, sender):
from alarmlist import AlarmList
dir = 'Media/Sounds/drums/'
sounds = ['Drums_01','Drums_02','Drums_03']
selected = self.v['tableview1'].selected_row[1]
now = datetime.now()
delay = self.date - now
notification.schedule('test notify', delay=delay.total_seconds(), sound_name=dir+sounds[selected])
a = AlarmList()
sender.superview.navigation_view.push_view(a.v)
notification.schedule
でアラームを設定してから、AlarmListのviewを呼び出していますね。
ここで気づいてほしいのが、AlarmListをtap_ok_buttonの中でimportしているということ。
このimport文をファイルの先頭に書くと、soundselect.py
の中でAlarmを呼び出した瞬間、循環importになってエラーになります。
まぁこの方法が最善、、、ではないかもしれませんが、循環しますよが言いたかっただけなので上手な避け方は別途調べてください。
っともうひとつ。ここまでの実装ではAlarmListクラスを定義しているalarmlist.py
にメイン処理を書いていますので、alarmlist.py
の中はクラスだけにして新たにメイン処理のスクリプトを描きましょう。
import ui
from alarmlist import AlarmList
a = AlarmList()
nv = ui.NavigationView(a.v)
nv.height = 500
nv.width = 500
nv.name = 'Alarm'
nv.present('sheet')
これでアラームが設定された後、アラーム一覧画面に戻ります!おもしろかっこいいぜ!
アラームを一覧化する
画面遷移ばかりに気を取られてすっかり忘れていましたね。まぁここまできたら完全にオマケ機能ですが、設定したアラームを一覧画面に出してあげましょう。
notification.schedule()
で設定したアラームは同じくnotification
モジュールの、get_scheduled()
で取得することができます。
あんまり拘ってファイル出力とかしてもしょうがないので、今回はこれ使っときます。
alarmlist.py
を編集しましょう。
import ui
from datepick import Datepick
import notification
from datetime import datetime
class AlarmList(object):
def __init__(self):
self.v = ui.load_view()
self.tv = self.v['tableview1']
self.v['button1'].action = self.tap_new_button
# get_sucheduled()はリストを返す
l = []
for a in notification.get_scheduled():
# fire_dateにはtimestamp(float型)が入っている
d = datetime.fromtimestamp(a['fire_date'])
l.append(d.strftime('%a %b %d %H %M'))
# TableViewに表示するデータソースの設定
self.ds = ui.ListDataSource(l)
self.v['tableview1'].data_source = self.ds
def tap_new_button(self, sender):
d = Datepick()
sender.superview.navigation_view.push_view(d.v)
アラーム自体はどうでもいいですが、これはこれでnotification
の使用方法だったりTableView
のデータ設定方法だったり、Pythonistaモジュールのあれこれに触れてますね。
ちなみにもうちょっと作り込むと、背景色をつけたりはもちろん、アラーム一覧画面でアラームを削除できるようにしたり、UI実装の極意にいろいろ触れることができます。アラームアプリひとつでもいろんな機能使うんですね。。。
(体裁はともかく、)アラーム一覧を表示することができました!
ちなみに通知されたものはnotification.get_scheduled
で取得できなくなるので、表示されなくなります。
おわりに
今回は決まった順番に画面遷移する方法を書きました。
このくらいできるようになると、UI実装の幅が広がって、いろいろと応用がきくようになってくるのではないかと思います。
また、深く掘り下げられる要素については記事書いていきたいと思います!
だいぶ長い記事になりましたが、読んでいただきありがとうございました!