[swift] タイマー処理と設定した秒数

@AppStorageはデータを永続化するUserDefaultsから値を読み込みする
UserDefaultはアプリで利用する値を保存する機能
L ここではtimer_valueというkeyにtimeValueの初期時10を導入している

    @State var timerHandler : Timer?
    @State var count = 0
    @AppStorage("timer_value") var timerValue = 10

1秒ごとに呼び出してcountを+1とし、残り0でタイマーを止める

    func countDownTimer() {
        count += 1
        
        if timerValue - count <= 0 {
            timerHandler?.invalidate()
        }
    }

タイマー開始

    func startTimer() {
        if let unwrapedTimerHandler = timerHandler {
            if unwrapedTimerHandler.isValid == true {
                return
            }
        }
        
        if timerValue - count <= 0 {
            count = 0
        }
        
        timerHandler = Timer.scheduledTimer(withTimeInterval: 1, repeats: true){
            _ in
            
            countDownTimer()
        }
    }

### タイマーの保存
SettingView.swift

@AppStorage("timer_value") var timerValue = 10

シミュレータで確認する

### アラートの表示

            .alert(isPresented: $showAlert){
                Alert(title: Text("終了"),
                      message: Text("タイマー終了時間です"),
                      dismissButton: .default(Text("OK")))
            }

すげえええええええええええ

[swift] タイマーアプリを作ろう

スタート、ストップボタンとカウントダウンの秒数を表示させるテキストを配置
スタートでカウントダウンを開始再開、ストップで一時停止
秒数設定で終了時間を設定できる
画面遷移はNavigationViewを利用、カウントダウン開始秒数をpickerを配置して設定 Backでタイマー画面に戻る

ContentView.swift
L NavigationViewは先頭画面であることを宣言
  L .toolbarはボタンを配置
  L navigationBarTrailingで右側に配置 navigationBarLeadingは右側、.bottomBarは下部

struct ContentView: View {
    var body: some View {
        NavigationView {
            VStack {
                Text("タイマー画面")
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing){
                    NavigationLink(destination: SettingView()){
                        Text("秒数設定")
                    }
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

### タイマーの色を定義
Asset.xcassetsでcolorsetを追加、「Any Appearance」を選択
218, 78, 122にRGBを設定
Darkモードで208, 68, 112で設定

同様にストップの色も定義する

ZStack {
                Image("backgroundTimer")
                    .resizable()
                    .ignoresSafeArea()
                    .aspectRatio(contentMode: .fill)
                VStack(spacing: 30.0) {
                    Text("残り10秒")
                        .font(.largeTitle)
                    HStack {
                        Button(action: {}){
                            Text("スタート")
                                .font(.title)
                                .foregroundColor(Color.white)
                                .frame(width: 140, height: 140)
                                .background(Color("startColor"))
                                .clipShape(Circle())
                        }
                        Button(action: {}){
                            Text("ストップ")
                                .font(.title)
                                .foregroundColor(Color.white)
                                .frame(width: 140, height: 140)
                                .background(Color("stopColor"))
                                .clipShape(Circle())
                        }
                    }
                }
            }

### Pickerを配置
SettingView.swift
L pickerStyle(.wheel)でPickerホイール配置

struct SettingView: View {
    
    @State var timerValue = 10
    
    var body: some View {
        
        ZStack {
            Color("backgroundSetting")
                .ignoresSafeArea()
            
            VStack {
                
                Picker(selection: $timerValue){
                    Text("10")
                        .tag(10)
                    Text("20")
                        .tag(20)
                    Text("30")
                        .tag(30)
                    Text("40")
                        .tag(40)
                    Text("50")
                        .tag(50)
                    Text("60")
                        .tag(60)
                } label: {
                    Text("選択")
                }
                .pickerStyle(.wheel)
            }
        }
    }
}

うむ、覚えることが広いな

[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