[swift] マップ検索アプリでMapKitとクロージャを学習2

ContentView.swift

struct ContentView: View {
    
    @State var inputText: String = ""
    @State var dispSearchKey: String = ""
    
    var body: some View {
        VStack {
            
            TextField("キーワード", text: $inputText, prompt: Text("キーワードを入力してください"))
                .onSubmit {
                    dispSearchKey = inputText
                }
                .padding()
            MapView(searchKey: dispSearchKey)
        }
    }
}

ContentView.swift

struct ContentView: View {
    
    @State var inputText: String = ""
    @State var dispSearchKey: String = ""
    @State var dispMapType: MKMapType = .standard
    
    var body: some View {
        VStack {
            
            TextField("キーワード", text: $inputText, prompt: Text("キーワードを入力してください"))
                .onSubmit {
                    dispSearchKey = inputText
                }
                .padding()
            
            ZStack(alignment: .bottomTrailing){
                
                MapView(searchKey: dispSearchKey, mapType: dispMapType)
                
                Button(action: {
                    if dispMapType == .standard {
                        dispMapType = .satellite
                    } else if dispMapType == .satellite {
                        dispMapType = .hybrid
                    } else if dispMapType == .hybrid {
                        dispMapType = .hybridFlyover
                    } else if dispMapType == .hybridFlyover {
                        dispMapType = .mutedStandard
                    } else {
                        dispMapType = .standard
                    }
                }) {
                    Image(systemName: "map")
                        .resizable()
                        .frame(width: 35.0, height: 35.0, alignment: .leading)
                }
                .padding(.trailing, 20.0)
                .padding(.bottom, 30.0)
            }
            
        }
    }
}

うーむ、これは凄いわ

[swift] マップ検索アプリでMapKitとクロージャを学習1

検索窓(TextField)、マップを表示するView(MapKit)を設置する
キーワードから緯度経度を検索 -> 緯度経度からピンの画面パーツ生成 -> ピンの画面パーツを地図画面に貼り付け
MapKitにはUIViewRepresentableを使用する
TextFieldでの文字入力と入力完了後(onCommit)の使い方を学ぶ
ContentView.swiftとMapView.swiftを作成する

SwiftUIのMapView.swiftを作成
アプリでMapを表示するにはMapKitをインポートする
MapKitは地図、衛星画像、ピン配置、住所から座標検索ができる

import SwiftUI
import MapKit

struct MapView: UIViewRepresentable {
    func makeUIView(context: Context) -> MKMapView {
        MKMapView()
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context){
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView()
    }
}

UIViewRepresentableを記載すると、makeUIView(viewの作成)とupdateUIView(viewの変更、再描画)が必要になる
UIViewRepresentableは、MKMapViewを使うためのラッパー

### 検索キーワードの設定
MapView.swift

struct MapView: UIViewRepresentable {
    
    let searchKey: String
    
    func makeUIView(context: Context) -> MKMapView {
        MKMapView()
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context){
        
        print(searchKey)
    }
}

struct MapView_Previews: PreviewProvider {
    static var previews: some View {
        MapView(searchKey: "東京タワー")
    }
}

ContentView.swift

struct ContentView: View {
    var body: some View {
        VStack {
            MapView(searchKey: "東京タワー")
        }
    }
}

-> シュミレーターを立ち上げると”東京タワー”と表示される

シミュレータでプログラムが実行される順番
 MyMapApp.swift -> ContentView.swift -> MapView.swift

### 検索キーワードから緯度経度を検索

    func updateUIView(_ uiView: MKMapView, context: Context){
        
        print(searchKey)
        
        let geocoder = CLGeocoder()
        geocoder.geocodeAddressString(
        searchKey ,
        completionHandler: { (placemarks, error) in
            
        if let unwrapPlacemarks = placemarks,
           let firstPlacemark = unwrapPlacemarks.first ,
           let location = firstPlacemark.location {
            
            let targetCoordinate = location.coordinate
            print(targetCoordinate)
            }
        })
    }

追加

            let pin = MKPointAnnotation()
            pin.coordinate = targetCoordinate
            pin.title = searchKey
            uiView.region = MKCoordinateRegion(
                center: targetCoordinate,
                latitudinalMeters: 500.0,
                longitudinalMeters: 500.0)

MKPointAnnotationはピンを置くための機能

地図の扱いは特殊ですね

[swift] viewの共通化

NewFile -> iOS SwiftUI view
BackgroundView.swiftで作成する

struct BackgroundView: View {
    
    let imageName: String
    
    var body: some View {
        Image(imageName)
            .resizable()
            .ignoresSafeArea()
            .aspectRatio(contentMode: .fill)
    }
}
struct BackgroundView_Previews: PreviewProvider {
    static var previews: some View {
        BackgroundView(imageName: "background")
    }
}

ContentView.swift

BackgroundView(imageName: "background")

テンプレート化もできるので
OK
早くDBまで行きたい

[swift] 楽器アプリの作成

HStackは横方向にViewをレイアウトする
Assets.xcassets に画像を配置する

### 背景画像の配置

    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea()
                .aspectRatio(contentMode: .fill)
        }
    }

HStackで配置

        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea()
                .aspectRatio(contentMode: .fill)
            HStack {
                Button(action: {}){
                    Image("cymbal")
                }
                Button(action: {}){
                    Image("guitar")
                }
            }
        }
    }

### mp3ファイルの取り込み
音源ファイルを読み込み、タップされたら音源を鳴らす
mp3をAssets.xcassetsに配置する
TargetMembershipがMyMusicになっている

### SoundPlayer.swifを追加
New Fileで Cocoa Touch Class を追加する
SoundPlayer: NSObject を追加する

iOSで音を鳴らすにはAVFoundationを読み込む
AVAudioPlayer は do catchで書く

import UIKit
import AVFoundation

class SoundPlayer: NSObject {
    let cymbalData = NSDataAsset(name: "cymbalSound")!.data
    var cymbalPlayer: AVAudioPlayer!
    
    func cymbalPlay() {
        do {
            cymbalPlayer = try AVAudioPlayer(data: cymbalData)
            cymbalPlayer.play()
        } catch {
            print("シンバルでエラーが発生しました!")
        }
    }
}

ContentView.swiftで呼び込む

struct ContentView: View {
    
    let soundPlayer = SoundPlayer()
    
    var body: some View {
        ZStack {
            Image("background")
                .resizable()
                .ignoresSafeArea()
                .aspectRatio(contentMode: .fill)
            HStack {
                Button(action: {
                    soundPlayer.cymbalPlay()
                }){
                    Image("cymbal")
                }
                Button(action: {}){
                    Image("guitar")
                }
            }
        }
    }
}

### 同じようにguitarも追加
SoundPlayer.swift

class SoundPlayer: NSObject {
    let cymbalData = NSDataAsset(name: "cymbalSound")!.data
    var cymbalPlayer: AVAudioPlayer!
    
    let guitarData = NSDataAsset(name: "guitarSound")!.data
    var guitarPlayer: AVAudioPlayer!
    
    func cymbalPlay() {
        do {
            cymbalPlayer = try AVAudioPlayer(data: cymbalData)
            cymbalPlayer.play()
        } catch {
            print("シンバルでエラーが発生しました!")
        }
    }
    
    func guitarPlay() {
        do {
            guitarPlayer = try AVAudioPlayer(data: guitarData)
            guitarPlayer.play()
        } catch {
            print("ギターでエラーが発生しました!")
        }
    }
}

ContentView.swiftはcymbalと同じ

プログラミングの原理原則は他の言語と同じだから、コツを掴めばいけそうやな

[swift] アイコンの設定

デバイスのディスプレイの大きさによってポイント数、画像サイズ、スケールなどが異なる

MakeAppIconで1枚の画像からアイコンを自動生成してくる

MyJanken/Assets.xcassets/AppIcon.appiconset にコピー&ペーストする

アイコンが追加されている

なんかiPhoneに派生して凄いことになってるな

[swift] じゃんけんアプリを作っていく2

変数では通常値を変更できないが、@Stateで値を更新できるようになる
swiftは型推論を実装している

swift基本型: int, Uint(符号なし), Float, Double, String, Bool

初期値をnullとして、タップされたらグーに変わる

        VStack {
            
            if answerNumber == 0 {
                Text("これからじゃんけんをします")
            } else if answerNumber == 1 {
                Image("gu")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                Text("グー")
            } else if answerNumber == 2 {
                
            } else {
                
            }
            
            
            Button(action: {
                print("タップされたよ!")
                answerNumber = Int.random(in: 1...3)
            }) {
                Text("じゃんけんする!")
            }
        }

数値をランダムに算出するものを関数(function)と呼ぶ

同じジャンケンが続かないようにする
repeat {} while で繰り返し処理する

            Button(action: {
                answerNumber = Int.random(in: 1...3)
                var newAnswerNumber = 0
                
                repeat {
                    newAnswerNumber = Int.random(in: 1...3)
                } while answerNumber == newAnswerNumber
                answerNumber = newAnswerNumber
            }) {
                Text("じゃんけんする!")
            }

スタイリング

                Text("じゃんけんする!")
                    .frame(maxWidth: .infinity)
                    .frame(height: 100)
                    .font(.title)
                    .background(Color.pink)
                    .foregroundColor(Color.white)

こちらで完成

var body: some View {
        
        VStack {
            
            if answerNumber == 0 {
                Spacer()
                Text("これからじゃんけんをします")
                    .padding(.bottom)
            } else if answerNumber == 1 {
                Image("gu")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                Text("グー")
                Spacer()
            } else if answerNumber == 2 {
                Image("choki")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                Text("チョキ")
                    .padding(.bottom)
                Spacer()
            } else {
                Image("pa")
                    .resizable()
                    .aspectRatio(contentMode: .fit)
                Text("パー")
                    .padding(.bottom)
                Spacer()
            }
            
            
            Button(action: {
                answerNumber = Int.random(in: 1...3)
                var newAnswerNumber = 0
                
                repeat {
                    newAnswerNumber = Int.random(in: 1...3)
                } while answerNumber == newAnswerNumber
                answerNumber = newAnswerNumber
            }) {
                Text("じゃんけんする!")
                    .frame(maxWidth: .infinity)
                    .frame(height: 100)
                    .font(.title)
                    .background(Color.pink)
                    .foregroundColor(Color.white)
            }
        }
    }

なるほど、感どころはわかったような気がする
swift独特の書き方は覚えないといかんね。

[swift] じゃんけんアプリを作っていく1

Image, Text, Buttonを配置する
TextとButtonに背景、文字色、文字サイズを設定
Imageにじゃんけん画像を切り替える

ファイルの役割
– MyJankenApp.swift: アプリケーションのエントリーポイント
– ContentView.swift: アプリの基本的な画面
– Assets.xcassets: 画像、音楽ファイル、アイコン、色などのリソース
– Preview Asset: Preview用のアセットカタログ

### 画像ファイルの取り込み
Assets.xcassetsに画像をドラッグする

### レイアウト構成
VStack(垂直)、HStack(水平)、ZStack(奥行き)があり、組み合わせで親ビュー、子ビューにもなる
レイアウト: ContentView – VStack – Image, Text, Button – Text

### UIパーツの配置

struct ContentView: View {
    var body: some View {
        Image("gu")
    }
}

Image(“gu”).resizable()でサイズを自動調整

struct ContentView: View {
    var body: some View {
        Image("gu")
            .resizable()
            .aspectRatio(contentMode: .fit)
    }
}

struct: 構造体、 swiftは構造体推奨
ContentView: 構造体名、 swiftはViewプロトコルに準拠
View: プロトコル、 resizableやaspectRatioを利用できる
var: 変数、 後から値の変更が可能。定数は不可
body: 変数名
some: 型  具体的な戻り値を隠すことができる
{}: クロージャー

swiftでは、名前付き型(プロトコル型、クラス型、構造体型、数字文字文字列などのデータ型)と複合型(名前のない型で関数型とタプル型)の2種類がある
returnは省略される

「shift」 + 「command」 + 「L」でLibraryを表示し、VStackを表示
縦のカーソルを確認する

struct ContentView: View {
    var body: some View {
        
        VStack {
            Image("gu")
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text("グー")
            Button(action: {
                print("タップされたよ!")
            }) {
                Text("じゃんけんする!")
            }
        }
    }
}

ボタンをクリックするとテキストが表示される

ここからif文などを使っていくのね
xcodeは大分使い易くなった^^

[swift] 基本操作

「shift」 + 「command」 + 「L」でLibraryを表示

VStackでまとめる

        VStack {
            Text("Hello, world!")
                .font(.largeTitle)
                .padding()
            Button(/*@START_MENU_TOKEN@*/"Button"/*@END_MENU_TOKEN@*/) {
                /*@START_MENU_TOKEN@*//*@PLACEHOLDER=Action@*/ /*@END_MENU_TOKEN@*/
            }
        }

VStackの他にHStackやZStackもある

Add Modifierでbackgroundを追加する

– textをwhiteにしてpaddingを追加する
– @State var outputText = “Hello, World!” で変数を宣言する
– outputText = “Hi, Swift!” を定義

struct ContentView: View {
    @State var outputText = "Hello, World!"
    
    var body: some View {
        
        VStack {
            Text(outputText)
                .font(.largeTitle)
                .padding()
            Button(action: {
                outputText = "Hi, Swift!"
            }) {
                Text("切り替えボタン")
                    .foregroundColor(Color.white)
                    .padding(.all)
                    .background(Color.blue)
            }
        }
        
    }
}

Canvas: Static Mode, Live Preview
シミュレータ: Xodeツールの一部としてインストールされている
実機: 全ての機能を確認

Canvasのライブプレビューでテストできるので、シミュレータを起動しなくても確認できる

iPhone12以外でもiPadなど様々なデバイスで確認できる

コードでcanvasのpreviewを変更

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewDevice("iPhone 8")
    }
}

複数デバイスのプレビュー

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .previewDevice("iPhone 8")
        ContentView()
            .previewDevice("iPad Pro (9.7-inch)")
    }
}

PinPreviewで表示を固定

### シミュレータの起動
Runを押す
シミュレータは時間がかかる、カメラは閲覧できない
シミュレータは停止してから画面を閉じる

なるほど、前のストーリーボードから大分仕様が変わったなwww

Kerasで学習済みモデルを保存・読み込む方法

Kerasのモデル保存は、「.h5」というHDF5拡張し(階層データ形式)で保存する
Hierarchical Data Formatの略で5はバージョン
CSVより早い

from tensorflow.keras.models import load_model

model = load_model('ETL7-model.h5')
print(model.summary())

Model: “sequential”
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
conv2d (Conv2D) (None, 30, 30, 32) 320

batch_normalization (BatchN (None, 30, 30, 32) 128
ormalization)

activation (Activation) (None, 30, 30, 32) 0

max_pooling2d (MaxPooling2D (None, 15, 15, 32) 0
)

dropout (Dropout) (None, 15, 15, 32) 0

conv2d_1 (Conv2D) (None, 13, 13, 64) 18496

batch_normalization_1 (Batc (None, 13, 13, 64) 256
hNormalization)

activation_1 (Activation) (None, 13, 13, 64) 0

conv2d_2 (Conv2D) (None, 11, 11, 64) 36928

batch_normalization_2 (Batc (None, 11, 11, 64) 256
hNormalization)

activation_2 (Activation) (None, 11, 11, 64) 0

max_pooling2d_1 (MaxPooling (None, 5, 5, 64) 0
2D)

dropout_1 (Dropout) (None, 5, 5, 64) 0

flatten (Flatten) (None, 1600) 0

dense (Dense) (None, 512) 819712

batch_normalization_3 (Batc (None, 512) 2048
hNormalization)

activation_3 (Activation) (None, 512) 0

dropout_2 (Dropout) (None, 512) 0

dense_1 (Dense) (None, 48) 24624

activation_4 (Activation) (None, 48) 0

=================================================================
Total params: 902,768
Trainable params: 901,424
Non-trainable params: 1,344
_________________________________________________________________

from tensorflow.keras.models import load_model
from PIL import Image
import numpy as np

model = load_model('ETL7-model.h5')

img = Image.open("test.jpg").convert('L')
img.thumbnail((32, 32))
img = np.array(img)
pred = model.predict(img[np.newaxis]) # numpyのnewaxis
print(np.argmax(pred)) 

load_modelで読み込みはできるが、それを使って操作する方法がわからん…

ETL8でひらがなだけでなく、漢字もできるとのこと。

ETLを使って手書きの文字認識をやりたい

ETL

使用条件

会員登録を行い、ETLをDLする

binaryから画像にする

import struct
from PIL import Image, ImageEnhance
import glob, os

RECORD_SIZE = 2052

outdir = "ETL7-img/"
if not os.path.exists(outdir): os.mkdir(outdir)

files = glob.glob("ETL7/*")
fc = 0
for fname in files:
	fc = fc + 1
	print(fname)

	f = open(fname, "rb")
	f.seek(0)
	i = 0
	while True:
		i = i + 1
		s = f.read(RECORD_SIZE)
		if not s: break
		r = struct.unpack('>H2sH6BI4H4B4x2016s4x', s)
		iF = Image.frombytes('F', (64, 63), r[18], 'bit', 4)
		iP = iF.convert('L')
		code_jis = r[3]
		dir = outdir + "/" + str(code_jis)
		if not os.path.exists(dir): os.mkdir(dir)
		fn = "{0:02x}-{1:02x}{2:04x}.png".format(code_jis, r[0], r[2])
		fullpath = dir + "/" + fn
		enhancer = ImageEnhance.Brightness(iP)
		iE = enhancer.enhance(16)
		iE.save(fullpath, "PNG")
print("ok")

pickleの作成

import numpy as np
import cv2
import matplotlib.pyplot as plt
import glob
import pickle

out_dir = "./ETL7-img"
im_size = 32

save_file = out_dir + "/JapaneseHiragana.pickle"
plt.figure(figsize=(9, 17))

hiraganadir = list(range(177, 223+1))
hiraganadir.append(166)
result = []

for i, code in enumerate(hiraganadir):
	img_dir = out_dir + "/" + str(code)
	fs = glob.glob(img_dir + "/*")
	print("dir=", img_dir)

	for j, f in enumerate(fs):
		img = cv2.imread(f)
		img_gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
		img = cv2.resize(img_gray, (im_size, im_size))
		result.append([i, img])

		if j == 3:
			plt.subplot(11, 5, i + 1)
			plt.axis("off")
			plt.title(str(i))
			plt.imshow(img, cmap="gray")

pickle.dump(result, open(save_file,  "wb"))
plt.show

正解率 0.973809540271759 loss 0.09567940980195999

なんか出来てるっぽい