[SwiftUI] イメージと図形の表示

assetにドロップして、メディアライブラリを開いてプレビューにドロップする

画像サイズに合わせて伸縮

        VStack {
            Image("chicago")
                .resizable(resizingMode: .stretch)
                .aspectRatio(contentMode: .fit)
                .frame(width: 300)
            Text("Hello World")
                .padding()
        }

伸縮率と位置の調整

        VStack {
            Image("chicago")
                .resizable(resizingMode: .stretch)
                .aspectRatio(contentMode: .fit)
                .scaleEffect(1.8)
                .offset(x: -70, y: -30)
                .frame(width: 200, height: 300)
                .clipped()
            Text("Hello World")
                .padding()
        }

オーバーレイを使って文字を重ねる

        VStack {
            Image("chicago")
                .resizable(resizingMode: .stretch)
                .aspectRatio(contentMode: .fill)
                .scaleEffect(1.8)
                .frame(width: 300, height: 400)
                .clipped()
                .overlay(
                    Text("Hello World")
                        .font(.title)
                        .fontWeight(.light)
                        .foregroundColor(Color.white)
                        .offset(x: 0, y: -50)
                )
            Text("Hello World")
                .padding()
        }

### 図形の作成と配置
Circle, Ellipse, Rectangle, RoundedRectangle, Capsule, rotationEffect(), stroke(), ZStack, position()

円形を描写

        Circle()
            .foregroundColor(.blue)
            .frame(width: 200, height: 200)

楕円形

        Ellipse()
            .foregroundColor(.blue)
            .frame(width: 200, height: 400)

四角形

        Rectangle()
            .foregroundColor(.blue)
            .frame(width: 200, height: 400)
        // 角丸四角形
        RoundedRectangle(cornerRadius: 50)
            .foregroundColor(.blue)
            .frame(width: 200, height: 400)
        // カプセル
        Capsule()
            .foregroundColor(.blue)
            .frame(width: 250, height: 100)

図形の塗り色

        Circle()
            .fill(Color.pink)
            .padding(50)

Colorライブラリの指定
L Asset -> Color set -> RGB指定 -> ライブラリからドロップして指定

    var body: some View {
        Circle()
            .foregroundColor(Color("Wakakusa"))
            .frame(width: 300, height: 300)
    }

図形の回転

        Ellipse()
            .foregroundColor(.orange)
            .frame(width: 200, height: 400)
            .rotationEffect(.degrees(45))
            .clipped()

図形を重ねて表示
L 下に書いた方が上に表示される

        ZStack {
            Ellipse()
                .stroke(lineWidth: 4)
                .foregroundColor(.pink)
                .frame(width: 100, height: 300)
            Ellipse()
                .stroke(lineWidth: 4)
                .foregroundColor(.purple)
                .frame(width: 100, height: 300)
                .rotationEffect(.degrees(30), anchor: .bottom)
            Ellipse()
                .stroke(lineWidth: 4)
                .foregroundColor(.green)
                .frame(width: 100, height: 300)
                .rotationEffect(.degrees(-30), anchor: .bottom)
        }

### ビューの画像効果
Image, Text, clipShape(), shadow(), rotation3DEffect(), ZStack

        Image("chicago")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 300)
            .clipShape(Circle())

ビューに影をつける

        Image("chicago")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 300)
            .clipShape(RoundedRectangle(cornerRadius: 20))
            .shadow(radius: 20)

ビューを回転する

        Image("chicago")
            .resizable()
            .aspectRatio(contentMode: .fill)
            .frame(width: 300, height: 400)
            .clipped()
            .rotationEffect(.degrees(10), anchor: .center)

ビューの角を丸める

        Text("Hello, world!")
            .font(.body)
            .frame(width: 150, height: 150)
            .border(Color.pink, width: 10)
            .cornerRadius(10)

ビューを定義して背景に使う

struct ContentView: View {
    var body: some View {
        Text("Hello, world!")
            .font(.largeTitle)
            .padding(15)
            .foregroundColor(.white)
            .background(ShapeView())
            .cornerRadius(50)
            .frame(width: 150, height: 150)
    }
}

struct ShapeView: View {
    var body: some View {
        ZStack {
            Rectangle().rotationEffect(.degrees(45))
            Rectangle().rotationEffect(.degrees(-45))
        }
        .foregroundColor(.green)
        .frame(width: 50, height: 150)
    }
}

ビューを3D回転
L rotation3DEffectでYを軸にして回転

    var body: some View {
        Text("春はあけぼの。夜雨やう白くなり行く、山ぎは少し灯て、紫だちたる雲の細くたなびきたる。")
            .fontWeight(.light)
            .font(.title)
            .frame(width: 250)
            .rotation3DEffect(.degrees(45), axis: (x:0, y:1, z:0))
    }

スタック全体を3D回転

    var body: some View {
        ZStack(){
            Image("chicago")
                .resizable()
                .aspectRatio(contentMode: .fill)
                .offset(x: -70, y: 0)
                .frame(width: 250, height: 400)
                .clipped()
            Text("ほととぎす\n鳴きつける方をながむれば\nただ有明の月ぞ残れる\n")
                .fontWeight(.light)
                .font(.title)
                .foregroundColor(.white)
                .padding()
                .offset(x: 0, y: -5)
                .frame(width: 250, height: 400)
        }
        .rotation3DEffect(.degrees(45), axis:(x:1, y:0, z:0))
    }

なるほど、覚えること沢山あるな

[SwiftUI] レイアウトの調整

元のテキスト

        VStack {
            Text("春はあけぼの")
            Text("夏は夜")
            Text("秋は夕暮")
            Text("冬はつとめて")
        }
        .font(.largeTitle)

行間と文字揃え

        VStack(alignment: .leading, spacing: 15.0) {
            Text("春はあけぼの")
            Text("夏は夜")
            Text("秋は夕暮")
            Text("冬はつとめて")
        }

スペーサーを使って位置調整

        VStack {
            Spacer()
            VStack(alignment: .trailing) {
                Text("知性の自転車")
                    .font(.largeTitle)
                    .fontWeight(.black)
                Text("Bicycle for the Mind.")
                    .italic()
            }
            Spacer()
            VStack(alignment: .trailing) {
                Text("憐れみは恋の始まり")
                    .font(.largeTitle)
                    .fontWeight(.black)
                Text("Pity is akin to love.")
                    .italic()
            }
            Spacer()
        }

パディングを使った余白調整
.offsetで位置をずらせる

        VStack {
            VStack(alignment: .trailing) {
                Text("知性の自転車")
                    .font(.largeTitle)
                    .fontWeight(.black)
                Text("Bicycle for the Mind.")
                    .italic()
                    .offset(x: -10, y: 0)
            }
            .padding(.top, 80)
            VStack(alignment: .trailing) {
                Text("憐れみは恋の始まり")
                    .font(.largeTitle)
                    .fontWeight(.black)
                Text("Pity is akin to love.")
                    .italic()
                    .offset(x: -10, y: 0)
            }
            .padding(.top, 20)
            Spacer()
        }

パディングを使いこなす

    var body: some View {
        VStack(alignment: .leading) {
            Text("1. 枕草子/清少納言")
                .padding([.leading, .bottom], 20.0)
            Text("2. 春はあけぼの")
            Text("3. やうやう白くなり行く")
            Text("4. 山ぎは少し灯て")
                .padding()
            Text("5. 紫立ちたる雲の細くたなびきたる")
        }
        .padding(.vertical, 50.0)
        .padding(.horizontal, 30.0)
        .font(.callout)
        .border(Color.pink, width: 2)
    }

殆どCSSと遜色ないな

[SwiftUI] テキストの装飾

VStackとHStack

HStack {
            Text("おはよう!")
                .padding()
            Text("Placeholder")
            Text("Placeholder 2")
        }

横並びか縦並びかの違い

式の値を返す変数
L 式の値が1つだけならreturnを省略できる

var num:Int {
    let result = 2 * 5
    return result
}

print(num)

return HStackのreturnが省略されている
read onlyでgetが省略されている

    var body: some View {
        HStack {
            Text("おはよう!")
                .padding()
            Text("Placeholder")
            Text("Placeholder 2")
        }
    }

getterとsetter

var radius = 10.0

var diameter: Double {
    get {
        radius * 2
    }
    set (length){
        radius = length / 2
    }
}

var around:Double {
    get {
        let length = 2 * radius * Double.pi
        return length
    }
    set(length) {
        radius = length / (2 * Double.pi)
    }
}

print("半径が\(radius)の時、直径の長さは\(diameter)")
diameter = 30
print("直径が\(diameter)の時、半径は\(radius)")
around = 100
print("円周の長さが\(around)の時、円の半径は\(radius)")

libraryからコードを配置

    var body: some View {
        VStack {
            HStack {
                Text("Hello, world")
                    .padding()
                Text(/*@START_MENU_TOKEN@*/"Placeholder"/*@END_MENU_TOKEN@*/)
            }
            HStack {
                Text(/*@START_MENU_TOKEN@*/"Placeholder"/*@END_MENU_TOKEN@*/)
                Text(/*@START_MENU_TOKEN@*/"Placeholder"/*@END_MENU_TOKEN@*/)
            }
        }
    }

### フォントや縦横サイズの設定
font(), fontWeight(), foregroundColor(), frame(), padding()

フォントサイズ編集

        VStack {
            Text("Bicycle for the Mind")
                .font(.title)
                .fontWeight(.thin)
                .padding(.all, 40)
            Text("知性の自転車")
                .foregroundColor(Color.red)
        }

Line Limitとフレーム

        VStack {
            Text("春はあけぼの。やうやう白くなりゆく山ぎは、すこしあかりて、紫だちたる雲のほそくたなびきたる。")
                .lineLimit(2)
                .frame(width: 200.0)
        }

alignment

        VStack {
            Text("The quick brown for jumps over the lazy dog.")
                .font(.largeTitle)
                .multilineTextAlignment(.trailing)
                .frame(width: 200.0)
        }

Border
L 色は、red, orange, yellow, green, mint, teal, cyan, blue, indigo, purple, pink, brown, white, gray, black, clear, primary, secondaryが選べる

        VStack {
            Text("Bicycle for the Mind")
                .font(.title)
                .frame(width: 200.0, height: 200.0)
                .border(Color.green, width: 5)
        }

font size

        VStack {
            Text("Hello World")
                .padding()
                .font(.system(size: 100))
        }

frameの中での位置指定

        VStack {
            Text("The quick brown for \n jumps over \n the lazy dog.")
                .frame(width: 250, height: 200, alignment: .bottomTrailing)
                .padding()
        }

.titleや.heavyは Font.title, Font.heavyのFontを省略している

Inherited
L 上位の階層の設定があった場合に引き継ぐ

        VStack {
            Text("春はあけぼの")
            Text("夏は夜")
                .foregroundColor(.red)
            Text("秋は夕暮")
            Text("冬はつとめて")
        }
        .font(.title)
        .foregroundColor(.blue)

テキストを連結して表示

        VStack {
            Text("No.").bold() + Text("123").font(.largeTitle).foregroundColor(.red)
        }

とっつきにくい印象だったが、触ってみるとeasyね。

Swiftシンタックスの基礎

### 変数、タプル、型、繰り返し

var tanka = 120
var ninzu = 3 + 2
var price = tanka * ninzu

600

var km = 10
km += 5
km += 5
km += 5
var value = 0
value += 2
value *= 5
value /= 2

### 型推論

var title = "為替計算"
var dollar = 250
var rate = 108.5

inspectorで型を調べる事ができる

宣言

var title:String
var dollar:Int
var rate:Double

title = "為替計算"
dollar = 250
rate = 108.5

### varとlet(定数)

var title = "為替計算"
var dollar:Double
var rate:Double

dollar = 250
rate = 108.5

let yen = dollar * rate

### 文字列

let name = "高橋"
let who = name + " さん"

数字から文字列

let tanaka = 240
let kosu = 3
let kingaku = String(tanaka * kosu) + "円です"

文字列に変数や式を埋め込む
 L \(変数) で文字列に変数や数式を直接埋め込む事ができる

let time = 9.95
let result = "タイムは\(time)秒でした"

### タプル

var greeting = ("Hello", "こんにちは")
var guest = ("直鳥", "なおとり", 24)
var point = (23.1, 51.2, 13.8)

型指定

var greeting:(String, String) = ("Hello", "こんにちは")
var guest:(String, String, Int) = ("直鳥", "なおとり", 24)
var point:(Double, Double, Double) = (23.1, 51.2, 13.8)

型推論と型エラー
L 個数が

var greeting = ("Hello", "こんにちは")
greeting = ("宜しく", 4649)
var guest = ("直鳥", "なおとり", 24)
guest = ("金田一", "きんだいち")

### タプルの要素取り出し
使わない要素の場合は”_” を使う

var guest = ("桑原", "くわばら", 34)
let(name, _, age) = guest
let user = name + "さん、" + "\(age)歳"
print(user)

ラベルが付いているタプル

var user = (name:"鈴木", point:30)
user.point += 5
print(user.name)
print(user.point)

ラベル付きタプル

var point:(x:Double, y:Double, z:Double)
point = (4.2, 3.5, 6.1)
print(point.x)

インデックス番号でアクセス

var value = (100, 200, 300)
print(value.1)

for文

let numList = [4, 8, 15, 16, 32, 42]
var sum = 0
for num in numList {
    sum += num
}
print("合計 \(sum)")

整数のレンジから数値を順に取り出す

for x in 0 ..< 360*2 {
    let radian = Double(x) * Double.pi/180
    let y = sin(radian)
    print(x, y)
}

rangeの値を使わないforループ

var stars = ""
for _ in 1 ... 5 {
    stars += "★"
    print(stars)
}

なるほど、アンダースコア(_)の使い方がわかった
swift以外でも良く出てきてたけど、よくわかってなかったんだよね

Pythonでfor文を使って{辞書: [{辞書},{辞書},{辞書}…]}のjsonを作りたい

ありたいてに言うと、下記のような構造のjsonをfor文で作りたい

{
	"item": [
		{
			"area": "東京都","population": 1300, "capital": "東京"
		},
		{
			"area": "北海道","population": 538, "capital": "札幌市"
		},
		{
			"area": "沖縄","population": 143, "capital": "那覇市"
		}	
	]
}

for文で辞書を配列に入れて、それを辞書の中に入れてjsonにすればOK

ys = cl.OrderedDict()
result = []
for item in data:
	print(item["at"][:16] +" " + item["anm"] + " " + item["mag"] + " " + item["maxi"] + " " + item["cod"][:5] + " " + item["cod"][5:11]+ " " + item["cod"][12:-1])
	data = cl.OrderedDict()
	data["at"] = item["at"][:16]
	data["anm"] = item["anm"]
	data["mag"] = item["mag"]
	data["maxi"] = item["maxi"]
	data["tokei"] = item["cod"][:5]
	data["hokui"] = item["cod"][5:11]
	data["depth"] = item["cod"][12:-1]

	result.append(data)

ys["item"] = result
print(ys)

with open("test.json", "w") as f:
    json.dump(ys, f, ensure_ascii=False)

pythonによるjson操作と辞書の理解が浅かったので、これ作るのに丸一日かかった orz…
なんてこったい

Pythonでjson作成

import json

str = {
	"東京":{
		"population": 1300,
		"capital": "東京"
	},
	"北海道": {
		"population": 538,
		"capital": "札幌市"
	},
	"沖縄":{
		"population": 143,
		"capital": "那覇市"
	}
}

with open("population.json", "w") as f:
	json.dump(str, f, ensure_ascii=False)

{“東京”: {“population”: 1300, “capital”: “東京”}, “北海道”: {“population”: 538, “capital”: “札幌市”}, “沖縄”: {“population”: 143, “capital”: “那覇市”}}

なるほどー

[swift] APIを使った検索機能の実装

お菓子の虜 web APIの情報を使ってiOSで表示する

お菓子の虜 Web API

e.g.: https://www.sysbird.jp/webapi/?apikey=guest&keyword=%E3%82%AB%E3%83%AC%E3%83%BC%E5%91%B3&format=json
https://www.sysbird.jp/webapi/?apikey=guest&keyword=%E3%82%AB%E3%83%AC%E3%83%BC%E5%91%B3&format=json&max=10

request parameters: id, type, year, keyword, max, order
response: status, count, item(id, name, maker, price, type, regist, url, image, comment)

@StateObject, @State, @Bindingを学ぶ
検索画面はContentView.swiftで一覧表示処理、OkashiData.swiftでカスタムクラスで実装

OkashiData.swift
L StructではObservableOjectは利用できない、 classで定義する必要がある
  L ObservableOjectはカスタムクラス内でデータの状態を管理するために利用

class OkashiData: ObservableObject {
    
    func searchOkashi(keyword: String) async {
        print(keyword)
    }
}

Taskは一連の流れを処理する
ContentView.swift

struct ContentView: View {
    
    @StateObject var okashiDataList = OkashiData()
    @State var inputText = ""
    
    var body: some View {
        VStack {
            TextField("キーワード", text: $inputText,
                      prompt: Text("キーワードを入力してください。"))
                .onSubmit {
                    Task {
                        await okashiDataList.searchOkashi(keyword: inputText)
                    }
                }
                .submitLabel(.search)
                .padding()
        }
    }
}

WebAPIのリクエストURLを組み立てる
OkashiData.swift

    func searchOkashi(keyword: String) async {
        print(keyword)
        
        guard let keyword_encode = keyword.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
            return
        }
        
        guard let req_url = URL(string:
            "https://www.sysbird.jp/webapi/?apikey=guest&format=json&keyword=\(keyword_encode)&max=10&order=r") else {
            return
        }
        print(req_url)
    }

レスポンスデータ(JSON)を記憶する構造体
L Itemとすることで複数の構造体を保持できる配列として保存
  L ?を付与してnilを許容するオプショナル型として宣言する

    struct ResultJson: Codable {
        struct Item: Codable {
            let name: String?
            let url: URL?
            let image: URL?
        }
        let item: [Item]?
    }

URLSessionでデータをダウンロード
URLSession.sharedで簡素に実行

        do {
            let(data, _) = try await URLSession.shared.data(from: req_url)
            
            let decoder = JSONDecoder()
            let json = try decoder.decode(ResultJson.self, from: data)
            
            print(json)
        } catch {
            print("エラーが出ました")
        }

最近のiOSはマルチコアプロセッサが搭載されている

### 取得したデータをListで一覧表示
Itemの構造体を作成し、List表示
L Identifiableに準拠すると、一意に識別できる型として定義できる
  L uuidを用いてランダムな一意の値を生成

import SwiftUI
import UIKit

struct OkashiItem: Identifiable {
    let id = UUID()
    let name: String
    let link: URL
    let image: URL
}

@StateObject、ObservableObjectを使用すると@Publishedを使用できる
プロパティラッパーはプロパティをラップして機能を追加する

            guard let items = json.item else {return}
            
            DispatchQueue.main.async {
                self.okashiList.removeAll()
            }
            
            for item in items {
                if let name = item.name,
                   let link = item.url,
                   let image = item.image {
                   let okashi = OkashiItem(name: name, link: link, image: image)
                    DispatchQueue.main.async {
                        self.okashiList.append(okashi)
                    }
                }
            }
            print(self.okashiList)

### リストで一覧表示

    var body: some View {
        VStack {
            TextField("キーワード", text: $inputText,
                      prompt: Text("キーワードを入力してください。"))
                .onSubmit {
                    Task {
                        await okashiDataList.searchOkashi(keyword: inputText)
                    }
                }
                .submitLabel(.search)
                .padding()
            List(okashiDataList.okashiList) { okashi in
                HStack {
                    AsyncImage(url: okashi.image) { image in
                        image
                            .resizable()
                            .aspectRatio(contentMode: .fit)
                            .frame(height: 40)
                    } placeholder: {
                        ProgressView()
                    }
                    Text(okashi.name)
                }
            }
        }
    }

### Webページの表示
SFSafariViewControllerでWebページを表示

SafariView.swift
L SafariServicesでアプリの中でsafariを起動する

import SafariServices

struct SafariView: UIViewControllerRepresentable {
    
    var url: URL
    
    func makeUIViewController(context: Context) -> SFSafariViewController {
        return SFSafariViewController(url: url)
    }
    
    func updateUIViewController(_ uiViewController: SFSafariViewController, context: Context){
        
    }
}

struct ContentView: View {
    
    @StateObject var okashiDataList = OkashiData()
    @State var inputText = ""
    @State var showSafari = false
    
    var body: some View {
        VStack {
            TextField("キーワード", text: $inputText,
                      prompt: Text("キーワードを入力してください。"))
                .onSubmit {
                    Task {
                        await okashiDataList.searchOkashi(keyword: inputText)
                    }
                }
                .submitLabel(.search)
                .padding()
            List(okashiDataList.okashiList) { okashi in
                
                Button(action: {
                    showSafari.toggle()
                }){
                    HStack {
                        AsyncImage(url: okashi.image) { image in
                            image
                                .resizable()
                                .aspectRatio(contentMode: .fit)
                                .frame(height: 40)
                        } placeholder: {
                            ProgressView()
                        }
                        Text(okashi.name)
                    }
                }
            }
        }
    }
}

これは凄い