20210115のiOSに関する記事は6件です。

pythonistaでダイエット・体温メモツールを作ってみた

はじめに

健康診断でメタボの仲間入りとなってしまい、ダイエットを決意しました。
手元にあるipadで、毎日の体重等を記録してグラフ化できたらモチベーションも
上がるかも。

環境

ipad + pythonista3

要件

・朝と夜の体重・血圧や脈拍・体温も記録する。
・歩数や体脂肪も記録する。
・その日の行動メモも記録できたら、後日の分析に役に立つかも。
・毎日のデータをグラフ化する。
・長く続いた時にデータを活用できるように、CSVでデータを書き出したい。

Ui

Downloads20210115_02healthmemo[1].png

グラフはコンソールに画像として表示されています。
Downloads20210115_03healthmemo[1].png

コード

メインスクリプト
 試行錯誤した経緯で、注釈がかなり多く入っています。
色々調べたコードの注釈が少なくて、流れを調べるのに苦労したので
同じように コード調べながら書いている人が注釈みて理解する助けに
なればいいな共います。
こんな注釈無くても理解できる方には、注釈だらけの汚いコードになって
しまっています。ごめんなさい。
あと、注釈は自分用メモも兼ねています。

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機能の付いたスクリプトって なぜか在りませんでした。
こちらも作ってみたので またアップしてみます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CGAffineTransformのパラメータが簡単に理解できるPlayground Code

アフィン変換を行うCGAffineTransformのパラメータをa, b, c, d, tx, tyと全部指定して変換したい場合、やり方がよくわからなくなることがあるので、使い方が簡単に理解できるPlayground Codeを作りました。

CGAffineTransformのパラメータについての説明

CGAffineTransformのパラメータは、アフィン変換の式にあてはめると次のようになります。

<アフィン変換の式>

拡大の場合は、aにxの拡大量、dにyの拡大量を指定します。
移動の場合は、txにxの移動量、tyにyの移動量を指定します。
回転の場合はパラメータの部分を次のようにします。

拡大と回転を組み合わせる場合はそれらをかけ合わせる必要があります。

例:x,yを2倍して、角度aだけ回転させる場合

参考:CGAffineTransformを知る

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/tokyoyoshida

Twitterでも発信しています。
https://twitter.com/jugemjugemjugem

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[iOS][SwiftUI]ネットワークのURLから画像表示(同期・非同期・キャッシュ・リンク・ウィジェット)

ウィジェット(widgets)内に画像を表示し、画像タップでURLスキームによるアプリ連携のために使用したViewViewModelになります。

ViewModel(画像データの同期・非同期・キャッシュ)

Does try? Data(contentsOf: URL) でキャッシュしてます
https://stackoverflow.com/a/57826757

URLImageViewModel.swift
import 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.swift
import 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.swift
import 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.swift
import SwiftUI

    struct WidgetEntryView: View {
        var body: some View {
            LinkURLImageView(url: URL(string: "タップ先のリンクのURL(widgets://)")!,
                             imageUrlString: "画像のURL",
                             isSyncURLImage: true)
        }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSからMacのRailsアプリをlocalhostでデバッグする時の設定

なんども忘れるので備忘録として

流れ

  • MacとiOSを同じWiFiネットワークに接続
  • Macの接続IPアドレスを確認
  • iOSで接続しているネットワークにプロキシを設定
  • iOSのプログラムソースの修正
  • サーバーをローカル起動

MacとiOSを同じWiFiネットワークに接続

MacとiOSは同じWiFiネットワークに接続。

Macの接続IPアドレスを確認

システム環境設定⇨ネットワーク⇨WifiからIPアドレスを確認。
networking1.png

networking.png

iOSで接続しているネットワークにプロキシを設定

設定アプリ⇨Wifi⇨iマーク⇨HTTPプロキシ⇨手動でサーバーとポートを指定。
networking5.png
networking4.png
networking3.png

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が分かれば、どの端末でも誰でもアクセスできるので、漫画喫茶などのネットワークでやる際は注意は必要

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む