[Swift] オブジェクトを見張ってビュー表示を更新

ObservableObjectプロトコル、@Published、@ObservedObjectを利用する

### クラス定義とプロパティを使った処理

struct ContentView: View {
    @ObservedObject var user = User()

    var body: some View {
        VStack(alignment:.leading, spacing:15){
            Group {
                TextField("名前", text: $user.name)
                TextField("身長", text: $user.tall)
                    .keyboardType(.numberPad)
            }.textFieldStyle(RoundedBorderTextFieldStyle())
            if !(user.name).isEmpty && !(user.tall).isEmpty {
                Text("\(user.name)さんは\(fitSize(tall: user.tall))")
            }
        }.frame(width: 250)
    }
}

class User: ObservableObject {
    @Published var name = ""
    @Published var tall = ""
}

func fitSize(tall:String) -> String {
    guard let height = Double(tall) else {
        return "???"
    }
    
    switch height {
    case 145..<155 :
        return "Sサイズです"
    case 155..<176 :
        return "Mサイズです"
    case 176..<185 :
        return "Lサイズです"
    default:
        return "適したサイズがありません"
    }
}

面白い
名前と身長をclassにする

[Swift] @Bindingを使ってビューを閉じるボタンを作る

struct ContentView: View {
    @State var isShow: Bool = false

    var body: some View {
        Button(action: {
            isShow = true
        }){
            Text("シートを表示")
        }
        .sheet(isPresented: $isShow){
            SomeView(isPresented: $isShow)
        }
    }
}

struct SomeView: View {
    @Binding var isPresented: Bool
    
    var body: some View {
        NavigationView {
            VStack {
                Image(systemName: "ladybug").scaleEffect(2.0)
                Text("Hello").font(.title2).padding()
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(Color(red: 0.9, green: 0.9, blue: 0.8))
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing){
                    Button {
                        isPresented = false
                    } label: {
                        Text("閉じる")
                    }
                }
            }
        }
    }
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
        SomeView(isPresented: Binding.constant(false))
    }
}

[Swift] バイディングとオブジェクトの共有

@Binding, @Published, @ObservedObject, @StateObject, @EnvironmentObject
変数のバインディング、オブジェクトの共有を行う

### 変数を別のビューの変数と紐づけて使う
– 紐付けて使う変数を @Binding で宣言する

struct ContentView: View {
    @State var isChecked_person1: Bool = false

    var body: some View {
        HStack {
            Text("担当者1のチェック")
            PersonCheckMark(isChecked: $isChecked_person1)
        }
    }
}

struct PersonCheckMark: View {
    @Binding var isChecked: Bool
    
    var body: some View {
        Button(action: {
            isChecked.toggle()
        }) {
            Image(systemName: isChecked ? "person.fill.checkmark" : "person")
                .foregroundColor(isChecked ? .blue : .gray)
                .scaleEffect(2.0)
                .frame(width: 40, height: 40)
        }
    }
}

### チェック担当者を2人に増やす

struct ContentView: View {
    @State var isChecked_person1: Bool = false
    @State var isChecked_person2: Bool = false

    var body: some View {
        VStack {
            HStack {
                Text("担当者1のチェック")
                PersonCheckMark(isChecked: $isChecked_person1)
            }
            HStack {
                Text("担当者2のチェック")
                PersonCheckMark(isChecked: $isChecked_person2)
            }
            Group {
                if isChecked_person1 && isChecked_person2 {
                    Text("全員チェック済み").foregroundColor(.blue)
                } else {
                    Text("チェック待ち").foregroundColor(.red)
                }
            }
            .font(.largeTitle)
            .padding(.top)
        }
        
    }
}

ほう、

[Swift] タブやスワイプでビュー切り替え

struct ContentView: View {
    @State var selectedTag = 1

    var body: some View {
        TabView(selection: $selectedTag){
            Text("Tab Content 1").tabItem { Text("Tab Label 1")}.tag(1)
            Text("Tab Content 2").tabItem { Text("Tab Label 2")}.tag(2)
            Text("Tab Content 3").tabItem { Text("Tab Label 3")}.tag(3)
        }
        .font(.largeTitle)
    }
}

### タブビューのビューを個別に宣言

struct ContentView: View {
    @State var selectedTag = 1

    var body: some View {
        TabView(selection: $selectedTag){
            HomeTabView().tabItem {
                Image(systemName: "house")
                Text("HOME")
            }.tag(1)
            WeatherTabView().tabItem {
                Image(systemName: "cloud.sun")
                Text("お天気")
            }.tag(2)
            NewsTabView().tabItem {
                Image(systemName: "newspaper")
                Text("ニュース")
            }.tag(3)
        }
    }
}

struct HomeTabView: View {
    var body: some View {
        VStack {
            Image(systemName: "music.note.house")
                .scaleEffect(x: 3.0, y: 3.0)
                .frame(width: 100, height: 100)
            Text("HOME").font(.system(size: 20))
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(red: 0.5, green: 0.9, blue: 0.9))
        .ignoresSafeArea()
    }
}

struct WeatherTabView: View {
    var body: some View {
        VStack {
            Image(systemName: "cloud.sun")
                .scaleEffect(x: 3.0, y: 3.0)
                .frame(width: 100, height: 100)
            Text("お天気ページ").font(.system(size: 20))
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(red: 1.0, green: 0.9, blue: 1.0))
        .ignoresSafeArea()
    }
}

struct NewsTabView: View {
    var body: some View {
        VStack {
            Image(systemName: "newspaper")
                .scaleEffect(x: 3.0, y: 3.0)
                .frame(width: 100, height: 100)
            Text("ニュースと解説").font(.system(size: 20))
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color(red: 0.9, green: 0.9, blue: 0.8))
        .ignoresSafeArea()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
        HomeTabView()
        WeatherTabView()
        NewsTabView()
    }
}

TabViewの機能か、勉強になるね。

[Swift] グリッドレイアウト

LazyVGrid, LazyHGrid, GridItem, fixed, flexible, adaptive

struct ContentView: View {
    let grids = Array(repeating: GridItem(.fixed(80)), count:4)
    
    var body: some View {
        ScrollView() {
            LazyVGrid(columns: grids){
                ForEach((1...100), id: \.self) {
                    num in Page(str: String(num))
                        .cornerRadius(8)
                        .frame(height: 60)
                }
            }
        }
    }
}

グリッドのサイズ指定の3つのモード
– LazyVGrid(columns:grids)のグリッドレイアウトで何列並べるか、列幅はどうするかは配列gridsで決まる
GridItem(_size: GridItem.Size = .flexible(), spacing: CGFloat? = nil, alignment:Alignment? = nil)

struct ContentView: View {
    let grids = [GridItem(.fixed(30), spacing: 10, alignment: .center),
                 GridItem(.fixed(50), spacing: 10),
                 GridItem(.fixed(240))]
    
    var body: some View {
        LazyVGrid(columns: grids, alignment: .leading, spacing: 20){
            ForEach(photoArray) {
                item in Image(systemName: "doc")
                Text(item.imageName).font(.caption)
                Text(item.title)
            }
        }.padding()
    }
}

struct PhotoData: Identifiable {
    var id = UUID()
    var imageName:String
    var title:String
}

var photoArray = [
    PhotoData(imageName: "IMG_0463", title: "台風で流された親柱"),
    PhotoData(imageName: "IMG_0495", title: "横須賀ヴェルニー記念講演"),
    PhotoData(imageName: "IMG_1378", title: "恋人たちの湘南平テレビ塔"),
    PhotoData(imageName: "IMG_1739", title: "赤い漁具倉庫1"),
    PhotoData(imageName: "IMG_1742", title: "赤い漁具倉庫2"),
    PhotoData(imageName: "IMG_2233", title: "江ノ電501系"),
    PhotoData(imageName: "IMG_2406", title: "茅ヶ崎漁港引き上げモーター小屋"),
    PhotoData(imageName: "IMG_2407", title: "茅ヶ崎漁港第二えぼし丸"),
    PhotoData(imageName: "IMG_2864", title: "相模川河口調整水門"),
    PhotoData(imageName: "IMG_2909", title: "つくばエキスポセンター H2ロケット")
]

struct ContentView: View {
    let grids = [GridItem(.fixed(150), spacing: 20, alignment: .leading),
                 GridItem(.fixed(20), spacing: 5, alignment: .leading),
                 GridItem(.fixed(20), alignment: .leading)]
    
    var body: some View {
        ScrollView(.horizontal){
            LazyHGrid(rows: grids, spacing:20){
                ForEach(photoArray) {
                    item in Image(item.imageName)
                        .resizable()
                        .aspectRatio(contentMode: .fit)
                        .cornerRadius(8)
                    Text(item.imageName).bold()
                    Text(item.title).font(.caption)
                }
            }.padding()
        }
        
    }
}

struct ContentView: View {
    let grids = [
        GridItem(.adaptive(minimum: 80, maximum:.infinity))
    ]
    
    var body: some View {
        ScrollView{
            LazyVGrid(columns: grids, alignment: .leading, spacing: 10){
                ForEach(1...100, id:\.self){
                    num in Ball(str: String(num))
                        .frame(width: 50, height: 50)
                }
                .padding()
            }
        }
        
    }
}

struct Ball: View {
    let str:String
    
    var body: some View {
        ZStack {
            Circle()
                .fill(Color.red)
            Text(str)
                .font(.title)
                .foregroundColor(.white)
        }
    }
}

うおおおおおおお、なんか凄いことになってるな…

[Swift] 複数のビューをスクロール表示

ContentView.swift

struct ContentView: View {
    
    var body: some View {
        ScrollView {
            LazyVStack{
                ForEach(0..<10) {
                    num in Page(str: String(num))
                        .frame(width:200, height: 150)
                        .cornerRadius(8)
                }
            }
        }
        .frame(width: 250, height: 500)
        .background(Color.gray.opacity(0.2))
    }
}

struct Page: View {
    let colors:[Color] = [.green, .blue, .pink, .orange, .purple]
    let str:String
    
    var body: some View {
        ZStack {
            colors.randomElement()
            Text(str)
                .font(.largeTitle)
                .foregroundColor(.white)
        }
    }
}

横スクロール

struct ContentView: View {
    let w:CGFloat = UIScreen.main.bounds.width-20
    
    var body: some View {
        VStack(alignment: .leading){
            Text("横スクロール").padding([.leading])
            ScrollView(.horizontal){
                LazyHStack(alignment: .center, spacing: 10){
                    ForEach(0..<10) {
                        num in Page(str: String(num))
                            .frame(width: w, height: 150)
                            .cornerRadius(8)
                    }
                }
            }
            .frame(height: 200)
            .background(Color.gray.opacity(0.2))
        }
    }
}

LazyVStackとLazyHStackで見えてるところをスクロールさせるのね
なるほど、少しずつキャズムに足を踏み入れることが出来る様になってきた…

[Swift] スクロールビュー

ScrollView, LazyVStack, LazyHStack, UIScreen.main.bounds, ForEach-in

PhotoData.swift

import Foundation

struct PhotoData: Identifiable {
    var id = UUID()
    var imageName:String
    var title:String
}

var photoArray = [
    PhotoData(imageName: "IMG_0463", title: "台風で流された親柱"),
    PhotoData(imageName: "IMG_0495", title: "横須賀ヴェルニー記念講演"),
    PhotoData(imageName: "IMG_1378", title: "恋人たちの湘南平テレビ塔"),
    PhotoData(imageName: "IMG_1739", title: "赤い漁具倉庫1"),
    PhotoData(imageName: "IMG_1742", title: "赤い漁具倉庫2"),
    PhotoData(imageName: "IMG_2233", title: "江ノ電501系"),
    PhotoData(imageName: "IMG_2406", title: "茅ヶ崎漁港引き上げモーター小屋"),
    PhotoData(imageName: "IMG_2407", title: "茅ヶ崎漁港第二えぼし丸"),
    PhotoData(imageName: "IMG_2864", title: "相模川河口調整水門"),
    PhotoData(imageName: "IMG_2909", title: "つくばエキスポセンター H2ロケット")
]

PhotoView.swift

import SwiftUI

struct PhotoView: View {
    var photo:PhotoData
    
    var body: some View {
        VStack {
            Image(photo.imageName)
                .resizable()
                .aspectRatio(contentMode: .fit)
            Text(photo.title)
                .bold()
                .padding(.top, 10)
                .padding(.bottom, 20)
        }
        .background(Color(red: 0.3, green: 0.8, blue: 0.5))
        .cornerRadius(8)
    }
}

struct PhotoView_Previews: PreviewProvider {
    static var previews: some View {
        PhotoView(photo:photoArray[0])
    }
}

写真データを取り込んでスクロール表示する

struct ContentView: View {
    
    var body: some View {
        ScrollView {
            LazyVStack(alignment: .center, spacing: 20){
                ForEach(photoArray) { photoData in
                    PhotoView(photo:photoData)
                }
            }
        }.padding(.horizontal)
    }
}

うおおおおおおおおお
これは凄いな

[Swift] ハーフモーダルビュー

struct ContentView: View {

    @State var isModal: Bool = false
    var body: some View {
        Button(action: {
            isModal = true
        }) {
            Text("Sheet テスト")
        }
        .sheet(isPresented: $isModal){
            SomeView()
        }
    }
}
struct ContentView: View {

    @State var isModal: Bool = false
    @State var counter:Int = 0
    
    var body: some View {
        VStack {
            Button(action: {
                isModal = true
            }) {
                Text("Sheetテスト")
            }
            .sheet(isPresented: $isModal, onDismiss: {countUp(}){
                SomeView()
            }
            .disabled(counter >= 3)
                Text("回数\(counter)")
                    .font(.title)
                    .paddin()
        }
    }
                   func countUp(){
                counter += 1
            }
}

なんか久しぶりにSwift触ると、全然頭がついていけないな
少しずつでも毎日継続してやらないと駄目だ

[Swift] URLSessionによるPOST

Button("Place Order"){
	Task {
		await placeOrder()
	}
}

func placeOrder() async {
	guard let encoded = try? JSONEncoder().encode(order) else {
		print("Failed to encode order")
		return
	}

	let url = URL(string: "https://hoge.com")!
	var request = URLRequest(url: url)
	request.setValue("application/json", forHTTPHeaderField: "Content-Type")
	request.httpMethod = "POST"

	do {
		let (data, _) = try await URLSession.shared.upload(for: request, from: encoded)
	} catch {
		print("Checkout failed.")
	}
}

どのようにデータをPOSTしているのか、なんとなくイメージはついた。
ああああ、Swiftやらなきゃあああああああああああああ
どうしよう、これ。。。

[SwiftUI] 確認ダイアログの表示

struct ContentView: View {
    @State private var isShowingDialog = false
    
    var body: some View {
        Button(action: {
            isShowingDialog = true
        }) {
            Label("削除ボタン", systemImage: "trash")
        }.confirmationDialog("注意!", isPresented: $isShowingDialog) {
            Button("削除する", role: .destructive){
                destructiveAction()
            }
            Button("キャンセル", role: .cancel){
                cancelAction()
            }
        } message: {
            Text("削除すると戻せません")
        }
    }
    func destructiveAction(){
        print("削除が選ばれた")
    }
    
    func cancelAction(){
        print("キャンセルが選ばれた")
    }
}

### 確認ダイアログのタイトル表示

    var body: some View {
        Button(action: {
            isShowingDialog = true
        }) {
            Label("削除ボタン", systemImage: "trash")
        }.confirmationDialog("注意!", isPresented: $isShowingDialog,
                             titleVisibility: .visible) {
            Button("選択A"){
            }
            Button("選択B"){
            }
            Button("削除する", role: .destructive){
                destructiveAction()
            }
            Button("キャンセル", role: .cancel){
                cancelAction()
            }
        } message: {
            Text("メッセージ、メッセージ、メッセージ、メッセージ") +
            Text("メッセージ、メッセージ、メッセージ、メッセージ")
        }
    }

buttonのactionの箇所は何も記載がないが、ここに実行文を書くのね。