20
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

PythonistaのUI実装で遊ぶ[画面遷移]

Last updated at Posted at 2019-12-23

はじめに

更新が遅くなってしまいましたが、引きつづきPythonistaのUI実装についてです!
前回までの記事に引き続いて、今回は画面遷移を実装しようと思います。
いやいやまだPythonista全然触ったこともないんだけどって方は以下の記事も読んでみていただけると幸いです。
(ストーリー性はないので、前提知識さえあれば下記記事読まなくてもこの記事は理解できます)

[過去記事]
Pythonista3のUI実装で遊ぶ[超超入門]
PythonistaのUI実装で遊ぶ[画面要素]
PythonistaのUI実装で遊ぶ[Actionの実装]

画面遷移

単一の画面で完結しているアプリはほとんどありませんよね。
PythonistaでUIを実装していると、画面遷移したくて震えがとまらなくなってしまうことがあるのではないでしょうか。わかります。わかりますとも。

というわけで今回は以下のように動く簡単なリマインダアプリを作ってみたいと思います。朝弱いけど、自分で作ったアラームならばなんか起きれる気がすんだよなーって方、必見です!
153D1C06-5EE3-41BF-A62B-91BDFC658C11.jpeg

青い四角が画面です。アラーム一覧画面が最初に開いて、時刻とサウンドを選択するとアラームが設定され、最初の一覧に戻るイメージですね。

画面を作る

まず画面をぜんぶ用意しちゃいましょう。
このへん画面作って処理作ってって作り方の順番はいろいろあると思いますが、今回はそんな複雑な画面でもないですし、それぞれの画面作ってから遷移の処理入れます。

アラーム一覧画面

Pythonista の「Script with UI」でタイトルをalermlistとすると、alarmlist.py と alarmlist.pyui が作成されます。pyuiの方を開いて以下のように要素を作成します。
BFD95721-0D01-402A-A2FC-347859584316.jpeg
画面サイズは適当に500*500ですが、やったことは以下です。
Custom Viewで画面を覆う
Table Viewを追加(デフォルト値のRow 1とかは消しときます)
Buttonを追加しTitleをNew Alarmに変更

New Alarm押して作ったアラームがここに一覧表示されるイメージですね。
今回は何もしていませんが、基本的には背景色変えたりとかすると思うのでCustom Viewは入れとけばよいかと思います。

時刻設定画面

alarmlistと同様にソースファイルdatepickを作成します。
datepick.pyuiを以下のように作成します。
74753B46-594E-42DC-84FA-257EFE83CCF4.jpeg
やったことは以下です。
Custom Viewで画面を覆う
Date Pickerを追加し、Modeを「Date and Time」に変更
Buttonを追加しTitleをOKに変更
日付を選択してOK押したら次のサウンド選択にいく予定の画面です。

サウンド選択画面

soundselectを作成して、pyuiを以下のように作成します。
E4D1C086-A16B-467C-9486-272A75E20A1B.jpeg
やったことはalarmlist.pyuiと一緒です。
ButtonのタイトルだけOKにしてあります。

各画面の処理

さてpyuiができたのでこんどは処理を実装していきます!

アラーム一覧画面から時刻設定画面への遷移

処理の実装

「アラーム一覧を表示する」という一番大事な処理を差し置いて、画面遷移処理だけを実装します。
ようするに、New Alarm押下でdatepickに遷移する処理です。
alermlist.pydatepick.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.pyDatepickクラスを呼び出して、New Alarmボタンが押下されたときにDatepick.present_viewを呼び出して画面を出していきたいわけですね。
先に言っときますけどこれだと期待通りの動作にはなりません。

ではうまくいかないことがわかっているままalarmlist.pyの方へ。

alermlist.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を実行すると以下のような画面が出ます。
IMG_2317.jpeg

さて、本番はここからです。ドキドキしながらNew Alarmを押してみましょう。
IMG_2318.jpeg

やったー!時刻選択画面に遷移したぞ!
よーし1回閉じて、続きの処理を実装していきましょう。。。ん?

IMG_2319.jpeg

×を押したら時刻選択画面が閉じて、最初の画面が後ろからでてきました。思ってたんと違う!
present()は実行すると「新しい画面を生成」するので、先の例のままでは次々と新しい画面が生成されていくだけです。

...てぇ...移してぇ...同じ画面のまま画面遷移してぇ...

NavigationViewクラスを使う

小見出しではありますがここが今回の記事でいちばん大切な箇所です。
ここまで読んでいただいてありがとうございます。

PythonistaのドキュメントにはNavigationViewクラスの説明としてだいたい以下のようなことが書いてあります。

NavigationViewは、ビューのスタック/階層を表示するためのインターフェイスを提供します。ナビゲーションビューの上部には、現在のビューの名前と戻るボタンを含むナビゲーションバーが表示されます。

操作ごとにビューを順番に表示したい場合には使ってねってことです。

まずはalarmlist.pyを以下のように書き換えます。

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()はせっかく実装しましたが不要です消します。

datepick.py
import ui

class Datepick(object):	
	def __init__(self):
		self.v = ui.load_view()

画面自体はalarmlist.py側で呼び出しているので、ここはload_viewするだけでよくなります。vは外から見えるようにしましょう。

実行してNew Alarmを押します。

IMG_2320.jpeg

遷移できました!
上にバーが追加されて、Backボタンが表示されていますね。
画面遷移がNavigationView上で行われているので、Backを押すと前の画面に戻ります。すごい!
×を押しても、前の画面が残ることなく、閉じることができます。すごい!!

時刻設定画面からサウンド選択画面への遷移

さて次は時刻設定画面からサウンド選択画面への遷移です。
さっきの遷移と同じように実装しましょう。

まずは遷移先のsoundselect.py

soundselect.py
import ui

class SoundSelect(object):
	def __init__(self):
		self.v = ui.load_view()

次に呼び出し側のdatepick.pyです。
いま作ったSoundSelectをimportします。

datepick.py
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に渡してやりましょう。

datepick.py
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側は、受け取った値をコンストラクタが受け取れるようにしましょう。

soundselect.py
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のテーブルに値を設定しましょう。
今回は動的に入れたりせず固定値とします。
IMG_2322.jpeg

さらにsoundselect.pyを以下のように修正します。

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を押下したら、最初のアラーム一覧画面に戻るようにします。
まずは、サウンド選択画面からアラーム一覧画面を呼び出すようにします。

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):
		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実装の極意にいろいろ触れることができます。アラームアプリひとつでもいろんな機能使うんですね。。。

IMG_2324.jpeg

(体裁はともかく、)アラーム一覧を表示することができました!
ちなみに通知されたものはnotification.get_scheduledで取得できなくなるので、表示されなくなります。

おわりに

今回は決まった順番に画面遷移する方法を書きました。
このくらいできるようになると、UI実装の幅が広がって、いろいろと応用がきくようになってくるのではないかと思います。

また、深く掘り下げられる要素については記事書いていきたいと思います!
だいぶ長い記事になりましたが、読んでいただきありがとうございました!

20
28
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
28

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?