- 投稿日:2021-01-15T22:34:42+09:00
pythonistaでダイエット・体温メモツールを作ってみた
はじめに
健康診断でメタボの仲間入りとなってしまい、ダイエットを決意しました。
手元にあるipadで、毎日の体重等を記録してグラフ化できたらモチベーションも
上がるかも。環境
ipad + pythonista3
要件
・朝と夜の体重・血圧や脈拍・体温も記録する。
・歩数や体脂肪も記録する。
・その日の行動メモも記録できたら、後日の分析に役に立つかも。
・毎日のデータをグラフ化する。
・長く続いた時にデータを活用できるように、CSVでデータを書き出したい。Ui
コード
メインスクリプト
試行錯誤した経緯で、注釈がかなり多く入っています。
色々調べたコードの注釈が少なくて、流れを調べるのに苦労したので
同じように コード調べながら書いている人が注釈みて理解する助けに
なればいいな共います。
こんな注釈無くても理解できる方には、注釈だらけの汚いコードになって
しまっています。ごめんなさい。
あと、注釈は自分用メモも兼ねています。healthmemo.py#! python3 #coding: utf-8 # 体重等を測ったら、簡単にメモしてcsv保存 # 体重・日時・血圧・脈・体脂肪率・体温・歩数を記録する。 ''' 開発履歴 20201029 Ver001 健康支援研修から体重等を記録し易くするメモアプリを作る事にした 20201031 Ver002 データ構造などをpcで作成し、ipadでui作成。ベータ版 20201114 Ver003 体重のグラフ表示を追加。グラフ化すると入力が面白くなる!! 20201117 Ver004 起動時loadとsaveのバックアップ作成。上書きでデータ消えた(T_T) 20201125 Ver004 グラフに歩数・体温・血圧を追加。バージョン変更無。 ★日付と朝と夜=午前午後から自動判別。csvに記録→グーグルスプレッドシートに追記+メールでバックアップを自動送信。 ★→自動追記とメール送信はしない。csvを共有してコピーするとした。 ★小数点は入力しない。3桁の数で入力して、自動変換。←変換しない。小数点入力無しにする。 ★メモの字数を自動カウント+警告を出すようにする。(200文字制限?) 、 ★縦横の両方で使えるようにする。キーボードに注意。できれば縦メイン。→iPad横のみ用とした。iphoneでは小さすぎた。 ★保存ボタン(メールの自動送信も含む)は、必要かも。 ★csvも日毎に1ファイル作って、pcでcsvを合成するファイル統合ソフト作った方がいいかな? →そうすると、iPad上でのcsvは1次元配列でもできるかもしれない。→csv一つに毎日のデータ追記にした。2次元配列。 ''' import ui import datetime import sys import io #gazou hennkou import csv import os #file sousa # import pandas 不可!pythonista3は、pandas使用不能。numpyとバージョン合わないらしい。csvで読むことにした。 import matplotlib.pyplot as plt from PIL import Image #global変数 一覧 nitiji_now = datetime.datetime.now() # 現在日時の取得 nitiji_on = nitiji_now.strftime('%Y/%m/%d_%H:%M:%S') # datetime関数から日時を取り出して、表示する文字の変数に代入 nitiji_kyou = str(nitiji_now.strftime('%Y/%m/%d')) #kyoufile = str('healthmemodata') + nitiji_now.strftime('%Y%m%d') + str('.csv') filename = str('healthmemodata.csv') graphname = str('healthmemograph.png') healthlist = [['年月日','体重朝','体重夜','血圧上朝','血圧下朝','脈拍朝','血圧上夜','血圧下夜','脈拍夜','体脂肪','歩数','メモ','体温朝','体温夜']] nitiji_asaban = 'AM' taijyuuAM = '001' taijyuuPM = '002' ketuatuueAM = '003' ketuatuuePM = '006' ketuatusitaAM = '004' ketuatusitaPM = '007' myakuhakuAM = '005' myakuhakuPM = '008' taionAM = '012' taionPM = '013' taisibou = '009' hosuu = '0010' memo = 'memo011' listnosaigonohi = False ########### # グラフ作成と表示 def graphsakusei(): # グラフ画像作成 xmin=0 xmax=100 dx=1 ymax=730 ymin=670 dy=25 if os.path.exists(graphname):# グラフ画像あれば、消す。無ければそのまま作成。 os.remove(graphname) else: pass x0 = [x[0] for x in healthlist]#0列目を抽出 del x0[0] x = range(len(x0)) y = [y[1] for y in healthlist] y1 = [y1[2] for y1 in healthlist] del y[0] del y1[0] plt.figure() #matplotでグラフ描く毎に最初に宣言しないと重ね書きされる。pythonista3の癖。 # 体重以外の項目を換算して表示 y2b = [y2b[10] for y2b in healthlist]#歩数 y2 = other_y(y2b) print(y2b) print(y2) plt.plot(x,y2,linestyle='dashed',marker='o',color='g',label='Hosuu')#緑 y3b = [y3b[9] for y3b in healthlist]#体脂肪 y3 = other_y(y3b) print(y3b) print(y3) plt.plot(x,y3,linestyle='dashed',marker='o',color='orchid',label='Tisibou')#紫 y4b = [y4b[12] for y4b in healthlist]#体温am y4 = other_y(y4b) plt.plot(x,y4,linestyle='dashed',marker='o',color='hotpink',label='TaionAM')#ピンク y5b = [y5b[3] for y5b in healthlist]#血圧上am y5 = other_y(y5b) plt.plot(x,y5,linestyle='dashed',marker='o',color='y',label='KetuatuUeAM')#黄色 y6b = [y6b[4] for y6b in healthlist]#血圧下am y6 = other_y(y6b) plt.plot(x,y6,linestyle='dashed',marker='o',color='gold',label='KetuatuSitaAM')#黄色 ''' y7b = [y7b[11] for y7b in healthlist]#memoの▲の日 y7 = other_y(y7b) plt.plot(x,y7,linestyle='dashed',marker='o',color='goldenrod',label='Memo')#茶色 ''' #plt.figure() #matplotでグラフ描く毎に宣言しないと重ね書きされる。最初に宣言pythonista3の癖。 plt.grid()#グリッドの表示 plt.plot(x,y,marker='o',color='red',label='AMkg')#グラフにデータインプット plt.plot(x,y1,marker='v',color='blue',label='PMkg') #plt.plot(x,y2,linestyle='dashed',marker='o',color='g',label='Hosuu') plt.ylim(ymin,ymax) #グラフの表示範囲を指示 plt.title('taijyuu kg') plt.xlabel('date') plt.ylabel('kg') #plt.xlim([xmin,xmax]) plt.legend(bbox_to_anchor=(1.01,1),loc='upper left',borderaxespad=0,fontsize=18)# 凡例の表示。 plt.subplots_adjust(right=0.8) # 右側の余白を追加して凡例を切れない様にする(グラフを左から80%以内に表示) plt.show() #グラフ表示 plt.savefig(graphname)#PNGでカレントに保存される pil_img = Image.open(graphname)#pilに画像読み込み imgg = pil2ui(pil_img) return imgg # 体重以外の項目を675-725の50の範囲内に換算した値のリストを返す def other_y(inputlist): # yyb = inputlist #其の他データを体重に合わせて表示(670-730→675−725の50で最大値最小値に換算して表示 #yyb = [yyb[10] for yyb in healthlist]#歩数 del yyb[0]#インデックスの先頭行を削除 yyb = [float(x) for x in yyb]# リスト全体をstrからfloatに変換 yymin2 = min2(yyb) #2番目に小さい値(自作関数) yyd=(float(max(yyb))-yymin2)/50 # [0 if c<15 else c for c in yyb] #空白値が12までなので、15以下を0に置換 yyout = [((i-yymin2)/yyd)+675 if i>15 else 0 for i in yyb] #yybのリストを順に15超は(i-yymin2)*yyd.15未満は0に置換 print('yyb;',yyb,'yymin2;',yymin2,'yyd;',yyd,'yyout;',yyout) return yyout # 2番目に小さい値を返す def min2(inputlist): # m1,m2=float('inf'),float('inf') # float('inf')は、無限大を示す for x in inputlist: #print(x) x = float(x) #引数がstrだとエラーになるのでfloatに変換。しかし、数字以外だとやっぱりエラー(^^)b if x == m1:#最小値が複数ある場合の除去用に必要な条件分岐 pass elif x < m1: m1,m2=x,m1 elif x < m2: m2 = x return m2 # pil <=> ui pilとios(ui)の使う画像データは異なるので 変換が必要。今回はpil→uiに変換してる。imgIn=pil def pil2ui(imgIn):#from PIL import Image が必要 with io.BytesIO() as bIO: # pilの画像データをiosのuiで使える画像データに変換する。import io必要 imgIn.save(bIO, 'PNG') imgOut = ui.Image.from_data(bIO.getvalue()) del bIO return imgOut ########### # メイン処理 def readdata(): # データファイルの読み込みリスト変数に代入 不使用 global healthlist # グローバル変数でないと各関数で読めない。ここで変更するのでglobal宣言必要 if os.path.exists(filename): healthlist =[] faile1 = open(filename,"r",encoding='utf-8') # ファイルを読み込む=r属性でオープンする。 healthlist = list(csv.reader(faile1)) # すべて読み込んでcsvオブジェクト→リストデータにする。 #for n in range(len(memocamdata)): # 一行づつ全部表示。rangeで数のカウント lenで要素数 # memocamdata[n] = memocamdata[n].strip("\n") # stripで指定文字消去。要素1づつに入てる、改行を消す faile1.close() # ファイルをクローズする。メモリ節約 else: healthlist = ['not data file'] return healthlist def writedata(healthlist): # グローバル変数のリストをデータファイルに書込み with open(filename,'w', newline='',encoding='utf-8') as f:# テキストファイルを作成。winはnweline='' を入れると安全? writer = csv.writer(f) # 二次元配列を一気に書込み writer.writerows(healthlist) # 二次元配列を一気に書込み def listgousei():#入力数値を二次元配列の最後列に追加。 global healthlist # グローバル変数でないと各関数で読めない。ここで変更するのでglobal宣言必要 healthlist0 = [nitiji_kyou,taijyuuAM,taijyuuPM,ketuatuueAM,ketuatusitaAM,myakuhakuAM,ketuatuuePM,ketuatusitaPM,myakuhakuPM,taisibou,hosuu,memo,taionAM,taionPM] healthlist.append(healthlist0) return healthlist def tail_csv(list0,n):#csvの最後からn行目までをリストで返す。文字列のまま。 #with open(file0) as f: #def ni file0 wo hikisuu de ireru #reader = csv.reader(f)#ファイルをCSVリーダーに変換 #next(reader)#ヘッダーを捨てる #rows = [row for row in reader]#全部行読んでリスト化 #tail = [list(map(float,row)) for row in rows[-n:]]#文字列なのでmap()でfloatに変換して、最後n行目まで抜き出す。(全部数字の場合のみ) tail = [list0[-n:]] return tail def dateck(sender):#リストの最下行が、今日か判別して代入 #tail = [] #1次元配列を宣言 global healthlist,taijyuuAM,taijyuuPM,ketuatuueAM,ketuatusitaAM,myakuhakuAM,ketuatuuePM,ketuatusitaPM,myakuhakuPM,taisibou,hosuu,memo,taionAM,taionPM,listnosaigonohi # グローバル変数でないと各関数で読めない。ここで変更するのでglobal宣言必要 tail = healthlist[-1:] # リストの最後行を抽出 if tail[0][0] == nitiji_kyou: listnosaigonohi = True taijyuuAM = tail[0][1] taijyuuPM = tail[0][2] ketuatuueAM = tail[0][3] ketuatusitaAM = tail[0][4] myakuhakuAM = tail[0][5] ketuatuuePM = tail[0][6] ketuatusitaPM = tail[0][7] myakuhakuPM = tail[0][8] taisibou = tail[0][9] hosuu =tail[0][10] memo = tail[0][11] taionAM = tail[0][12] taionPM = tail[0][13] g_name = str('体重AM; ') + str(float(taijyuuAM)/10) + str('kg') # 後ろに言葉を足す。 sender.superview['ttaijyuuAM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('体重PM; ') + str(float(taijyuuPM)/10) + str('kg') # 後ろに言葉を足す。 sender.superview['ttaijyuuPM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('血圧上AM; ') + ketuatuueAM + str('mmHg(135)') # 後ろに言葉を足す。 sender.superview['tketuatuueAM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('血圧下AM; ') + ketuatusitaAM + str('mmHg(85)') # 後ろに言葉を足す。 sender.superview['tketuatusitaAM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('脈拍AM; ') + myakuhakuAM + str('回/分(60-90)') # 後ろに言葉を足す。 sender.superview['tmyakuhakuAM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('血圧上PM; ') + ketuatuuePM + str('mmHg(135)') # 後ろに言葉を足す。 sender.superview['tketuatuuePM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('血圧下PM; ') + ketuatusitaPM + str('mmHg(85)') # 後ろに言葉を足す。 sender.superview['tketuatusitaPM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('脈拍PM; ') + myakuhakuPM + str('回/分(60−90)') # 後ろに言葉を足す。 sender.superview['tmyakuhakuPM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('体脂肪率; ') + str(float(taisibou)/10) + str('%(10−19)') # 後ろに言葉を足す。 sender.superview['ttaisibou'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('歩数; ') + hosuu + str('歩(1万)') # 後ろに言葉を足す。 sender.superview['thosuu'].title = g_name # テキストビューに反映する。左右重要。 g_name = memo # 後ろに言葉を足す。 sender.superview['tmemo'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('体温AM; ') + str(float(taionAM)/10) + str('度') # 後ろに言葉を足す。 sender.superview['ttaionAM'].title = g_name # テキストビューに反映する。左右重要。 g_name = str('体温PM; ') + str(float(taionPM)/10) + str('度') # 後ろに言葉を足す。 sender.superview['ttaionPM'].title = g_name # テキストビューに反映する。左右重要。 else: pass return ########### # load save create ボタン def loadinput(sender): global healthlist,taijyuuAM,taijyuuPM,ketuatuueAM,ketuatusitaAM,myakuhakuAM,ketuatuuePM,ketuatusitaPM,myakuhakuPM,taisibou,hosuu,memo,taionAM,taionPM # グローバル if os.path.exists(filename): readdata() dateck(sender) else: sender.superview['lasttext'].title = str('データファイルがありません。データを作成しました。') # テキストビューに反映する。左右重要。 createinput(sender) img = graphsakusei() sender.superview['graphview'].image = img sender.superview['txtcsv'].text = str(healthlist) def saveinput(sender): global healthlist backfilename = filename[:-4] + '_bk.csv' if os.path.exists(backfilename):#バックアップがあるか確認して、有れば消す os.remove(backfilename) else: pass if os.path.exists(filename): os.rename(filename,backfilename)#バックアップファイルにリネーム if listnosaigonohi: #print(backfilename) healthlist.pop(-1) #pop;リストの最後の要素を取得するメソッドだけど、結果として最後の要素を抜く #print(healthlist) listgousei() #print(healthlist) writedata(healthlist) sender.superview['lasttext'].title = str('healthlistdata保存完了') # テキストビューに反映 else: listgousei() writedata(healthlist) sender.superview['lasttext'].title = str('healthlistdata保存完了') # テキストビューに反映 else: sender.superview['lasttext'].title = str('データファイルがありません。') # テキストビューに反映する。左右重要。 def createinput(sender): if os.path.exists(filename): sender.superview['lasttext'].title = str('データファイルが既に有ります。') # テキストビューに反映する。左右重要。 else: #listgousei() writedata(healthlist) sender.superview['lasttext'].title = str('healthlistdata新規作成完了') # テキストビューに反映 def deleteinput(sender): if os.path.exists(filename): os.remove(filename) sender.superview['lasttext'].title = str('healthlistdata削除完了') # テキストビューに反映 else: sender.superview['lasttext'].title = str('データファイルがありません。') # テキストビューに反映する。左右重要。 ########### # 体重等の入力ボタン def taijyuuAMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('体重AM; ') + str(float(f_name)/10) + str('kg') # 後ろに言葉を足す。 sender.superview['ttaijyuuAM'].title = g_name # テキストビューに反映する。左右重要。 global taijyuuAM taijyuuAM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def taijyuuPMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('体重PM; ') + str(float(f_name)/10) + str('kg') # 後ろに言葉を足す。 sender.superview['ttaijyuuPM'].title = g_name # テキストビューに反映する。左右重要。 global taijyuuPM taijyuuPM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def ketuatuueAMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('血圧上AM; ') + f_name + str('mmHg(135)') # 後ろに言葉を足す。 sender.superview['tketuatuueAM'].title = g_name # テキストビューに反映する。左右重要。 global ketuatuueAM ketuatuueAM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def ketuatusitaAMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('血圧下AM; ') + f_name + str('mmHg(85)') # 後ろに言葉を足す。 sender.superview['tketuatusitaAM'].title = g_name # テキストビューに反映する。左右重要。 global ketuatusitaAM ketuatusitaAM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def myakuhakuAMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('脈拍AM; ') + f_name + str('回/分(60-90)') # 後ろに言葉を足す。 sender.superview['tmyakuhakuAM'].title = g_name # テキストビューに反映する。左右重要。 global myakuhakuAM myakuhakuAM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def ketuatuuePMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('血圧上PM; ') + f_name + str('mmHg(135)') # 後ろに言葉を足す。 sender.superview['tketuatuuePM'].title = g_name # テキストビューに反映する。左右重要。 global ketuatuuePM ketuatuuePM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def ketuatusitaPMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('血圧下PM; ') + f_name + str('mmHg(85)') # 後ろに言葉を足す。 sender.superview['tketuatusitaPM'].title = g_name # テキストビューに反映する。左右重要。 global ketuatusitaPM ketuatusitaPM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def myakuhakuPMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('脈拍PM; ') + f_name + str('回/分(60−90)') # 後ろに言葉を足す。 sender.superview['tmyakuhakuPM'].title = g_name # テキストビューに反映する。左右重要。 global myakuhakuPM myakuhakuPM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def taisibouinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('体脂肪率; ') + str(float(f_name)/10) + str('%(10−19)') # 後ろに言葉を足す。 sender.superview['ttaisibou'].title = g_name # テキストビューに反映する。左右重要。 global taisibou taisibou = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def hosuuinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('歩数; ') + f_name + str('歩(1万)') # 後ろに言葉を足す。 sender.superview['thosuu'].title = g_name # テキストビューに反映する。左右重要。 global hosuu hosuu = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def memoinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name # 後ろに言葉を足す。 sender.superview['tmemo'].title = g_name # テキストビューに反映する。左右重要。 global memo memo = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def taionAMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('体温AM; ') + str(float(f_name)/10) + str('度') # 後ろに言葉を足す。 sender.superview['ttaionAM'].title = g_name # テキストビューに反映する。左右重要。 global taionAM taionAM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def taionPMinput(sender): # 数値を入れて標準するボタン f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = str('体温PM; ') + str(float(f_name)/10) + str('度') # 後ろに言葉を足す。 sender.superview['ttaionPM'].title = g_name # テキストビューに反映する。左右重要。 global taionPM taionPM = f_name sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 ########### # 数字入力ボタン def b01_click(sender): text00 = "1" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b02_click(sender): text00 = "2" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b03_click(sender): text00 = "3" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b04_click(sender): text00 = "4" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b05_click(sender): text00 = "5" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b06_click(sender): text00 = "6" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b07_click(sender): text00 = "7" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b08_click(sender): text00 = "8" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b09_click(sender): text00 = "9" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b0_click(sender): text00 = "0" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def b00_click(sender): text00 = "00" f_name = sender.superview['text1'].text # textfieldのnameが[''] g_name = f_name + text00 # 後ろに言葉を足す。 sender.superview['text1'].text = g_name # テキストビューに反映する。左右重要。 def bclear_click(sender): f_name = sender.superview['text1'].text # textfieldのnameが[''] sender.superview['text1'].text = str("") # テキストビューに反映する。左右重要。 def bmemoread_click(sender): sender.superview['text1'].text = memo # テキストビューに反映する。左右重要。 ########### # if os.path.exists(filename): readdata() tail = healthlist[-1:] # リストの最後行を抽出 if tail[0][0] == nitiji_kyou: listnosaigonohi = True taijyuuAM = tail[0][1] taijyuuPM = tail[0][2] ketuatuueAM = tail[0][3] ketuatusitaAM = tail[0][4] myakuhakuAM = tail[0][5] ketuatuuePM = tail[0][6] ketuatusitaPM = tail[0][7] myakuhakuPM = tail[0][8] taisibou = tail[0][9] hosuu =tail[0][10] memo = tail[0][11] taionAM = tail[0][12] taionPM = tail[0][13] else: writedata(healthlist) v = ui.load_view() v.present('fullscreen')uiで生成されたコードをそのままアップしただけです。
healthmemo.pyui[ { "nodes" : [ { "nodes" : [ ], "frame" : "{{32, 52}, {572, 41}}", "class" : "TextField", "attributes" : { "action" : "", "font_size" : 20, "frame" : "{{254, 368}, {200, 32}}", "spellchecking_type" : "default", "class" : "TextField", "uuid" : "6F710D34-0CD5-4805-9A21-C94951CFA56D", "alignment" : "left", "text" : "", "autocorrection_type" : "default", "name" : "text1", "font_name" : "<System>" }, "selected" : false }, { "nodes" : [ ], "frame" : "{{436, 150}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b01_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "1", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b01", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{436, 310}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b03_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "3", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b03", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{524, 230}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b07_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "7", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b07", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{436, 390}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b04_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "4", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b04", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{524, 550}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b00_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "00", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b00", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{436, 470}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b05_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "5", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b05", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{436, 230}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b02_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "2", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b02", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 470}, {94, 72}}", "class" : "Button", "attributes" : { "action" : "loadinput", "image_name" : "iob:ios7_cloud_download_outline_32", "frame" : "{{472, 284}, {80, 32}}", "title" : "load", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(0.266667,1.000000,0.266667,1.000000)", "class" : "Button", "name" : "bload", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{524, 310}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b08_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "8", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b08", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{524, 470}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b0_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "0", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b0", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{524, 390}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b09_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "9", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b09", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 150}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "taijyuuAMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "体重AM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "ttaijyuuAM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 190}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "taijyuuPMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "体重PM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "ttaijyuuPM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 230}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "ketuatuueAMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "血圧上AM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tketuatuueAM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 12}, {572, 32}}", "class" : "Button", "attributes" : { "action" : "", "frame" : "{{344, 496}, {80, 32}}", "title" : "日付・・・", "uuid" : "61026267-1FAC-45B5-9261-F7077490FA9E", "font_bold" : true, "class" : "Button", "name" : "lasttext", "font_size" : 20 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 270}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "ketuatusitaAMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "血圧下AM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tketuatusitaAM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 350}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "ketuatuuePMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "血圧上PM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tketuatuuePM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 310}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "myakuhakuAMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "脈拍AM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tmyakuhakuAM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 390}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "ketuatusitaPMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "血圧下PM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tketuatusitaPM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{32, 430}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "myakuhakuPMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "脈拍PM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tmyakuhakuPM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 190}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "hosuuinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "歩数", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "thosuu", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 230}, {194, 72}}", "class" : "Button", "attributes" : { "action" : "memoinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "memo", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "tmemo", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 150}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "taisibouinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "体脂肪 %", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "ttaisibou", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 390}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "taionAMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "体温AM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "ttaionAM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 430}, {194, 32}}", "class" : "Button", "attributes" : { "action" : "taionPMinput", "frame" : "{{344, 496}, {80, 32}}", "title" : "体温PM", "uuid" : "A187D39B-77D9-4CD0-B4B9-AD98BD2B7460", "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "class" : "Button", "background_color" : "RGBA(0.771226,0.771226,0.771226,1.000000)", "name" : "ttaionPM", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{524, 150}, {80, 72}}", "class" : "Button", "attributes" : { "action" : "b06_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "6", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "b06", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 470}, {90, 72}}", "class" : "Button", "attributes" : { "action" : "createinput", "font_size" : 15, "frame" : "{{472, 284}, {80, 32}}", "title" : "create", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(0.266667,1.000000,0.266667,1.000000)", "class" : "Button", "name" : "bcreate", "image_name" : "iob:leaf_32" }, "selected" : false }, { "nodes" : [ ], "frame" : "{{134, 470}, {92, 72}}", "class" : "Button", "attributes" : { "action" : "saveinput", "font_size" : 15, "frame" : "{{472, 284}, {80, 32}}", "title" : "save", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(0.266667,1.000000,0.266667,1.000000)", "class" : "Button", "name" : "bsave", "image_name" : "iob:ios7_cloud_upload_outline_32" }, "selected" : false }, { "nodes" : [ ], "frame" : "{{332, 470}, {96, 72}}", "class" : "Button", "attributes" : { "action" : "deleteinput", "image_name" : "iob:ios7_trash_32", "frame" : "{{472, 284}, {80, 32}}", "title" : "delete", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(0.266667,1.000000,0.266667,1.000000)", "class" : "Button", "font_size" : 15, "name" : "bdelete" }, "selected" : false }, { "nodes" : [ ], "frame" : "{{332, 550}, {184, 72}}", "class" : "Button", "attributes" : { "action" : "bclear_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "clear", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "bclear", "font_size" : 50 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{234, 310}, {194, 72}}", "class" : "Button", "attributes" : { "action" : "bmemoread_click", "frame" : "{{472, 284}, {80, 32}}", "title" : "memoread", "uuid" : "A94A4D9E-3A5F-4687-8759-C78625266BD8", "background_color" : "RGBA(1.000000,0.844444,0.533333,1.000000)", "class" : "Button", "name" : "bmemoread", "font_size" : 15 }, "selected" : false }, { "nodes" : [ ], "frame" : "{{640, 150}, {346, 312}}", "class" : "ImageView", "attributes" : { "alpha" : 1, "border_width" : 1, "frame" : "{{462, 334}, {100, 100}}", "border_color" : "RGBA(1.000000,0.533333,0.300000,1.000000)", "class" : "ImageView", "uuid" : "2D875A02-DC26-4EEE-A03C-1CD264CACCBE", "corner_radius" : 0, "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "name" : "graphview", "image_name" : "iob:arrow_graph_down_right_256" }, "selected" : false }, { "nodes" : [ ], "frame" : "{{640, 470}, {346, 286}}", "class" : "TextView", "attributes" : { "uuid" : "DC7E0A0D-573D-487E-9D7D-527DD4E5EBF6", "font_size" : 10, "background_color" : "RGBA(1.000000,1.000000,1.000000,1.000000)", "frame" : "{{412, 284}, {200, 200}}", "editable" : true, "alignment" : "left", "autocorrection_type" : "default", "text" : "", "font_name" : "<System>", "spellchecking_type" : "default", "class" : "TextView", "name" : "txtcsv", "flex" : "WH" }, "selected" : false } ], "frame" : "{{0, 0}, {1024, 768}}", "class" : "View", "attributes" : { "name" : "", "enabled" : true, "border_color" : "RGBA(0.000000,0.000000,0.000000,1.000000)", "background_color" : "RGBA(0.833333,1.000000,1.000000,1.000000)", "tint_color" : "RGBA(0.000000,0.478000,1.000000,1.000000)", "flex" : "" }, "selected" : false } ]最後に、
pythonisutaで 写真に手書きスケッチとかできるツールのスクリプトは
いくつか見つかったのですが、zoom機能の付いたスクリプトって なぜか在りませんでした。
こちらも作ってみたので またアップしてみます。
- 投稿日:2021-01-15T21:03:22+09:00
AVCaptureVideoDataOutputのCMSampleBufferからCVPixelBufferにアクセスするときのお作法
CVPixelBufferを取得する場合は
CVPixelBufferLockBaseAddress
でロックを確保してpixelBufferにアクセス、終わったらCVPixelBufferUnlockBaseAddress
でアンロックするDetecting Human Body Poses in an Image より
// Attempt to lock the image buffer to gain access to its memory. guard CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) == kCVReturnSuccess else { return } // Create Core Graphics image placeholder. var image: CGImage? // Create a Core Graphics bitmap image from the pixel buffer. VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &image) // Release the image buffer. CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) DispatchQueue.main.sync { delegate.videoCapture(self, didCaptureFrame: image) }captureOutput(_:didOutput:from:) | Apple Developer Documentation
- 投稿日:2021-01-15T18:50:40+09:00
CGAffineTransformのパラメータが簡単に理解できるPlayground Code
アフィン変換を行うCGAffineTransformのパラメータをa, b, c, d, tx, tyと全部指定して変換したい場合、やり方がよくわからなくなることがあるので、使い方が簡単に理解できるPlayground Codeを作りました。
CGAffineTransformのパラメータについての説明
CGAffineTransformのパラメータは、アフィン変換の式にあてはめると次のようになります。
拡大の場合は、aにxの拡大量、dにyの拡大量を指定します。
移動の場合は、txにxの移動量、tyにyの移動量を指定します。
回転の場合はパラメータの部分を次のようにします。
拡大と回転を組み合わせる場合はそれらをかけ合わせる必要があります。
Playground Codeでしていること
UILabelを5つ作り、何もしない、拡大する、移動する、回転するとそれら全部を組み合わせた組み合わせるといったアフィン変換を使っています。
Playground//: A UIKit based Playground for presenting user interface import UIKit import PlaygroundSupport class MyViewController : UIViewController { override func loadView() { func buildLabel(_ num: Int) -> UILabel { let label = UILabel() label.frame = CGRect(x: 150, y: num * 100 + 50, width: 100, height: 20) label.backgroundColor = .red label.textColor = .black label.text = "変換なし" return label } func buildView() -> [UILabel]{ var labels = [UILabel]() let view = UIView() view.backgroundColor = .white for i in 0...4 { let label = buildLabel(i) view.addSubview(label) labels.append(label) } self.view = view return labels } let labels = buildView() _ = { // 拡大 let a: CGFloat = 2 // xの拡大量 let b: CGFloat = 0 let c: CGFloat = 0 let d: CGFloat = 2 // yの拡大量 let tx: CGFloat = 0 let ty: CGFloat = 0 let transform = CGAffineTransform( a: a, b: b, c: c, d: d, tx: tx, ty: ty ) labels[1].transform = transform labels[1].text = "拡大" }() _ = { // 移動 let a: CGFloat = 1 let b: CGFloat = 0 let c: CGFloat = 0 let d: CGFloat = 1 let tx: CGFloat = 30 // xの移動量 let ty: CGFloat = 30 // yの移動量 let transform = CGAffineTransform( a: a, b: b, c: c, d: d, tx: tx, ty: ty ) labels[2].transform = transform labels[2].text = "移動" }() _ = { // 回転 let r = -(CGFloat.pi / 4) let a: CGFloat = cos(r) let b: CGFloat = sin(r) let c: CGFloat = -sin(r) let d: CGFloat = cos(r) let tx: CGFloat = 0 let ty: CGFloat = 0 let transform = CGAffineTransform( a: a, b: b, c: c, d: d, tx: tx, ty: ty ) labels[3].transform = transform labels[3].text = "回転" }() _ = { // 組み合わせ let r = -(CGFloat.pi / 4) let a: CGFloat = 2*cos(r) let b: CGFloat = 2*sin(r) let c: CGFloat = 2*cos(r) let d: CGFloat = 2*cos(r) let tx: CGFloat = 30 // xの移動量 let ty: CGFloat = 30 // yの移動量 let transform = CGAffineTransform( a: a, b: b, c: c, d: d, tx: tx, ty: ty ) labels[4].transform = transform labels[4].text = "組み合わせ" }() } } // Present the view controller in the Live View window PlaygroundPage.current.liveView = MyViewController()最後に
NoteではiOS開発、とくにCoreML、ARKit、Metalなどについて定期的に発信しています。
https://note.com/tokyoyoshidaTwitterでも発信しています。
https://twitter.com/jugemjugemjugem
- 投稿日:2021-01-15T15:43:55+09:00
[iOS][SwiftUI]ネットワークのURLから画像表示(同期・非同期・キャッシュ・リンク・ウィジェット)
ウィジェット(widgets)内に画像を表示し、画像タップでURLスキームによるアプリ連携のために使用したViewとViewModelになります。
ViewModel(画像データの同期・非同期・キャッシュ)
Does try? Data(contentsOf: URL) でキャッシュしてます
https://stackoverflow.com/a/57826757URLImageViewModel.swiftimport SwiftUI final class URLImageViewModel: ObservableObject { @Published var downloadData: Data? = nil let url: String init(url: String, isSync: Bool = false) { self.url = url if isSync { self.downloadImageSync(url: self.url) } else { self.downloadImageAsync(url: self.url) } } func downloadImageAsync(url: String) { guard let imageURL = URL(string: url) else { return } let cache = URLCache.shared let request = URLRequest(url: URL(string: url)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad) if let data = cache.cachedResponse(for: request)?.data { self.downloadData = data }else { DispatchQueue.global().async { let data = try? Data(contentsOf: imageURL) DispatchQueue.main.async { self.downloadData = data } } } } func downloadImageSync(url: String) { guard let imageURL = URL(string: url) else { return } let cache = URLCache.shared let request = URLRequest(url: URL(string: url)!, cachePolicy: URLRequest.CachePolicy.returnCacheDataElseLoad) if let data = cache.cachedResponse(for: request)?.data { self.downloadData = data }else { let data = try? Data(contentsOf: imageURL) self.downloadData = data } } }View(画像表示)
URLImageView.swiftimport SwiftUI struct URLImageView: View { @ObservedObject var viewModel: URLImageViewModel var body: some View { if let imageData = self.viewModel.downloadData { if let image = UIImage(data: imageData) { return Image(uiImage: image).resizable().scaledToFit() } else { return Image(uiImage: UIImage()).resizable().scaledToFit() } } else { return Image(uiImage: UIImage()).resizable().scaledToFit() } } }View(リンク付き画像表示)
LinkURLImageView.swiftimport SwiftUI struct LinkURLImageView: View { let url: URL let imageUrlString: String let isSyncURLImage: Bool init(url: URL, imageUrlString: String, isSyncURLImage: Bool = false) { self.url = url self.imageUrlString = imageUrlString self.isSyncURLImage = isSyncURLImage } var body: some View { Link(destination: url) { let imageViewModel = URLImageViewModel(url: imageUrlString, isSync: isSyncURLImage) URLImageView(viewModel: imageViewModel) } } }使用例
ウィジェット(widgets)では非同期が使用できないため、同期通信を使用します。
(非同期はisSyncURLImageにfalseを指定)WidgetEntryView.swiftimport SwiftUI struct WidgetEntryView: View { var body: some View { LinkURLImageView(url: URL(string: "タップ先のリンクのURL(widgets://)")!, imageUrlString: "画像のURL", isSyncURLImage: true) } }
- 投稿日:2021-01-15T14:33:29+09:00
iOSからMacのRailsアプリをlocalhostでデバッグする時の設定
なんども忘れるので備忘録として
流れ
- MacとiOSを同じWiFiネットワークに接続
- Macの接続IPアドレスを確認
- iOSで接続しているネットワークにプロキシを設定
- iOSのプログラムソースの修正
- サーバーをローカル起動
MacとiOSを同じWiFiネットワークに接続
MacとiOSは同じWiFiネットワークに接続。
Macの接続IPアドレスを確認
システム環境設定⇨ネットワーク⇨WifiからIPアドレスを確認。
iOSで接続しているネットワークにプロキシを設定
設定アプリ⇨Wifi⇨iマーク⇨HTTPプロキシ⇨手動でサーバーとポートを指定。
iOSのプログラムソースの修正
static let v1Url: String = { #if DEBUG return "http://192.xxx.xxx.2/api/v1/" #endif return "https://xxxxxxxx.com/api/v1/" }()サーバーをローカル起動
bundle exec rails s -b 0.0.0.0
-b 0.0.0.0
を指定しないとlocalhost以外からだとアクセスができない
同一ネットーワークでかつIPが分かれば、どの端末でも誰でもアクセスできるので、漫画喫茶などのネットワークでやる際は注意は必要
- 投稿日:2021-01-15T13:06:27+09:00
Xcode 12でUIDatePickerの背景色が変わらなくなってしまった
問題のコード
datePicker.backgroundColor = .orange if #available(iOS 13.4, *) { datePicker.preferredDatePickerStyle = .wheels }Xcode 12対応をしていてこんなコードを書いたら背景色が変わらなくなってしまったときの調査メモ。
調査メモ
let datePicker = UIDatePicker() datePicker.backgroundColor = .orange //=> UIExtendedSRGBColorSpace 1 0.5 0 1 datePicker.preferredDatePickerStyle = .wheels //=> UIExtendedGrayColorSpace 0 0 datePicker.backgroundColor = .orange //=> UIExtendedSRGBColorSpace 1 0.5 0 1 datePicker.preferredDatePickerStyle = .inline //=> (nil) datePicker.backgroundColor = .orange //=> UIExtendedSRGBColorSpace 1 0.5 0 1 datePicker.preferredDatePickerStyle = .compact //=> (nil)スタイルを設定すると背景色が初期化されちゃいますよっと。
修正後のコード
if #available(iOS 13.4, *) { datePicker.preferredDatePickerStyle = .wheels } datePicker.backgroundColor = .orange