[Swift] 現在地を表示して移動をフォロー

LocationManager.swift

class LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {
    
    let manager = CLLocationManager()
    @Published var region = MKCoordinateRegion()
    
    override init() {
        super.init()
        manager.delegate = self
        manager.requestWhenInUseAuthorization()
        manager.desiredAccuracy = kCLLocationAccuracyBest
        manager.distanceFilter = 2
        manager.startUpdatingLocation()
    }
    
    func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]){
        locations.last.map {
            let center = CLLocationCoordinate2D(
                latitude: $0.coordinate.latitude,
                longitude: $0.coordinate.longitude
            )
            
            region = MKCoordinateRegion(
                center: center,
                latitudinalMeters: 1000.0,
                longitudinalMeters: 1000.0
            )
        }
    }
}

ContentView.swfit

import SwiftUI
import MapKit

struct ContentView: View {
    
    @ObservedObject var manager = LocationManager()
    @State var trackingMode = MapUserTrackingMode.follow

    var body: some View {
        Map(coordinateRegion: $manager.region,
            showsUserLocation: true,
            userTrackingMode: $trackingMode)
            .edgesIgnoringSafeArea(.bottom)
    }
}

まずまず

[Swift] 地図にアノテーションを表示

ContentView.swift

import SwiftUI
import MapKit

struct Spot: Identifiable {
    let id = UUID()
    let latitude: Double
    let longitude: Double
    var coordinate: CLLocationCoordinate2D {
        CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
    }
}

struct ContentView: View {
    
    let spotlist = [
        Spot(latitude: 35.6834843, longitude: 139.7644207),
        Spot(latitude: 35.6790079, longitude: 139.7675881),
        Spot(latitude: 35.6780057, longitude: 139.7631035)
    ]
    
    @State var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(
            latitude: 35.6805702,
            longitude: 139.7675359
        ),
        latitudinalMeters: 1000.0,
        longitudinalMeters: 1000.0
    )

    var body: some View {
        Map(coordinateRegion: $region,
            annotationItems: spotlist,
            annotationContent: {spot in MapMarker(coordinate: spot.coordinate, tint: .orange)})
            .edgesIgnoringSafeArea(.bottom)
    }
}

この辺はGoogleMapAPIと似てるなー

[Swift] Map()で地図を作る

Map()で地図を表示する

import SwiftUI
import MapKit

struct ContentView: View {
    
    @State var region = MKCoordinateRegion(
        center: CLLocationCoordinate2D(
            latitude: 35.6805702,
            longitude: 139.7675359
        ),
        latitudinalMeters: 1000.0,
        longitudinalMeters: 1000.0
    )

    var body: some View {
        Map(coordinateRegion: $region)
            .edgesIgnoringSafeArea(.bottom)
    }
}

地図機能はいつ見ても興奮します。

[Swift] Color拡張

ContentView.swift

extension Color {
    static var hagiiro: Color {
        return Color(red: 223/255, green: 87/255, blue: 143/255, opacity: 1.0)
    }
}

struct ContentView: View {

    var body: some View {
        ZStack{
            Circle()
                .foregroundColor(.hagiiro)
                .frame(width: 200, height:200)
            Text("萩色")
                .foregroundColor(.white)
                .font(.title)
        }
    }
}

そりゃできるよね。

[Swift] クラス定義

クラスはオブジェクトの仕様を定義したもの
L どんな属性と機能を持ち合わせたオブジェクトを作るのかを定義した設計書
L クラス定義に基づいて作ったオブジェクトをインスタンスと言う

classの書き方

class className {
	var variable:type = initial

	init(引数:type) {
		 self.variable = 引数
	}

	function fun(引数:type) -> void {
	 	return x
	}
}

playground

import UIKit

var greeting = "Hello, playground"

class MyFriend {
    var name:String
    var age:Int
    
    init(name:String, age:Int){
        self.name = name
        self.age = age
    }
    
    func hello() -> String {
        let message = "Hello! \(name)です。\(age)歳です。"
        return message
    }
}

let friend1 = MyFriend(name: "植木", age: 31)
let friend2 = MyFriend(name: "桜", age: 26)

let str1 = friend1.name + "と" + friend2.name + "は友達です。"
let str2 = friend1.name + "は" + String(friend1.age) + "歳です。"
friend2.age += 1
let str3 = friend2.name + "は誕生日で" + String(friend2.age) + "歳になりました。"

print(str1)
print(str2)
print(str3)

### 継承

class GoodFriend: MyFriend {
    let fortune = ["大吉", "吉", "小吉", "凶"]
    
    func uranai() -> String {
        let index = Int.random(in: 0 ..<fortune.count)
        let result = "今日の運勢は" + fortune[index] + "です!"
        return result
    }
    
    func who() -> String {
        return name + "です。よろしく!"
    }
}

### スーパークラス初期化

    var nickname:String
    init(name: String, age: Int, nickname: String){
        self.nickname = nickname
        super.init(name: name, age:age)
    }

### extensionを使ってクラス拡張

class Player {
    var name: String = ""
    func hello() {
        print("やあ!" + name)
    }
}

extension Player {
    var who: String {
        get {
            return name
        }
        set(value){
            name = value
        }
    }
    func bye(){
        print("またね!" + name)
    }
}

let user = Player()
user.who = "賢治"
user.hello()
user.bye()

これは数重ねるだけだなー

[Swift] どこかれでも共有できる@EnvironmentObject

クラス、メンバー、プロトコル、継承、クラス拡張などを知る

ShareData.swift

class ShareData: ObservableObject {
    @Published var isOn = false
    @Published var num = 1
}

SettingView.swift

struct SettingView: View {
    
    @EnvironmentObject var setData: ShareData
    @Binding var isPresented: Bool
    
    var body: some View {
        NavigationView {
            VStack {
                Toggle(isOn: $setData.isOn){
                    Text("設定: \(setData.isOn ? "ON": "OFF")")
                }.frame(width: 250)
                Stepper(value: $setData.num, in: 1...5){
                    Text("★ :\(setData.num)")
                }
                .frame(width: 250)
                .font(.title2)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .background(Color(red: 0.9, green: 0.9, blue: 0.5))
                .toolbar {
                    ToolbarItem(placement: .navigationBarTrailing){
                        Button("閉じる"){
                            isPresented = false
                        }
                    }
                }
            }
        }
    }
}

struct SettingView_Previews: PreviewProvider {
    static var previews: some View {
        SettingView(isPresented: Binding.constant(false))
            .environmentObject(ShareData())
    }
}

ContentView.swift

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

    var body: some View {
        VStack{
            GroupBox(label: Label("設定", systemImage: "gearshape")){
                Text("\(setData.isOn ? "ON" : "OFF")")
                if setData.isOn {
                    Text(String(repeating: "★", count: setData.num))
                }
            }.frame(width: 300)
            Button (action: {
                isShow = true
            }) {
                Label("設定を変える", systemImage: "ellipsis.circle")
            }
            .padding()
            .sheet(isPresented: $isShow){
                SettingView(isPresented: $isShow)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(ShareData())
    }
}
@main
struct LinkURLSampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(ShareData())
        }
    }
}

なんかよくわからんくなってきた。
かなり修行が必要やな

[Swift] @StateObjectと@ObservedObject

@StateObjectを利用することで親ビューの再描画でオブジェクトが初期化されなくなる

ContentView.swift

struct ContentView: View {
    @State var isShow = true

    var body: some View {
        VStack (alignment: .leading, spacing: 20){
            ValueView1()
            ValueView2()
            
            Toggle(isOn: $isShow){
                
            }.frame(width: 50).padding(.top, 30)
            if isShow {
                Text("Hello, World!").font(.largeTitle)
            }
            Spacer()
        }
        .padding()
    }
}

struct ValueView2: View {
    @StateObject var maker = ValueMaker()
    
    var body: some View {
        VStack (alignment: .leading, spacing: 10){
            Text("\(maker.value)")
                .font(.title)
                .foregroundColor((maker.value > 0.8) ? .white : .gray)
                .background((maker.value > 0.8) ? Color.red : Color.white)
            HStack {
                Text("カウンタ:")
                Text("\(maker.counter)").font(.largeTitle)
            }
        }
        .background(Color.blue.opacity(0.3))
        .frame(width: 200, height: 80)
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
        ValueView1()
        ValueView2()
    }
}

ValueMarker.swift

class ValueMaker: ObservableObject {
    
    @Published var value: Double
    @Published var counter: Int = 0
    private var timer: Timer
    
    init() {
        value = 0.0
        timer = Timer()
        start()
    }
    
    func start(){
        timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
            _ in
            self.value = Double.random(in: 0 ... 1)
            if self.value > 0.8 {
                self.counter += 1
            }
        }
    }
}

struct ValueView1: View {
    @ObservedObject var maker = ValueMaker()
    
    var body: some View {
        VStack (alignment: .leading, spacing: 10){
            Text("\(maker.value)")
                .font(.title)
                .foregroundColor((maker.value > 0.8) ? .white : .gray)
                .background((maker.value > 0.8) ? Color.red : Color.white)
            HStack {
                Text("カウンタ:")
                Text("\(maker.counter)").font(.largeTitle)
            }
        }
        .background(Color.yellow.opacity(0.3))
        .frame(width: 200, height: 80)
    }
}

なんかうまくいかんなー

[Swift] リストのスワイプアクションを処理

BookData.swift

import Foundation

struct Book: Identifiable, Equatable {
    var id = UUID()
    var title:String
    var author:String
    var isRead = false
}

class Books: ObservableObject {
    @Published var booksData = [
        Book(title: "風の又三郎", author: "宮沢賢治"),
        Book(title: "人間失格", author: "太宰治"),
        Book(title: "坊ちゃん", author: "夏目漱石"),
        Book(title: "遠野物語", author: "柳田國男"),
        Book(title: "生まれいずる悩み", author: "有島武郎"),
        Book(title: "舞姫", author: "森鴎外"),
        Book(title: "人間椅子", author: "江戸川乱歩"),
        Book(title: "人間レコード", author: "夢野久作"),
        Book(title: "山月記", author: "中島敦")
    ]
    
    func toggleIsRead(_ item: Book){
        guard let index = booksData.firstIndex(of: item) else { return }
        booksData[index].isRead.toggle()
    }
}

ContentView.swift

struct ContentView: View {
    @ObservedObject var books = Books()

    var body: some View {
        List(books.booksData) {
            item in
            BookView(item)
                .swipeActions(edge: .leading){
                    Button {
                        books.toggleIsRead(item)
                    } label: {
                        if item.isRead {
                            Label("未読にする", systemImage: "book.closed")
                        } else {
                            Label("既読にする", systemImage: "book.fill")
                        }
                    }.tint(.blue)
                }
        }.listStyle(.plain)
    }
}

@ViewBuilder
func BookView(_ item:Book) -> some View {
    VStack(alignment: .leading){
        Text(item.title).bold()
        Text(item.author)
    }
    .foregroundColor(item.isRead ? .gray : .black)
    .frame(height: 80)
}

なるほどー

[Swift] 刻々と変わるオブジェクトの値を表示

ValueMaker.swift

import Foundation

class ValueMaker: ObservableObject {
    
    @Published var value: Double = 0.0
    private var timer = Timer()
    
    func start() {
        timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) {
            _ in
            self.value = Double.random(in: 0 ... 1)
        }
    }
}

ContentView.swift

struct ContentView: View {
    @ObservedObject var maker = ValueMaker()

    var body: some View {
        VStack (alignment: .leading, spacing: 10){
            Text("\(maker.value)")
                .font(.largeTitle)
                .foregroundColor((maker.value > 0.8) ? .white : .gray)
                .background((maker.value > 0.8) ? Color.red : Color.white)
            ZStack {
                Rectangle().stroke(.gray)
                Rectangle()
                    .foregroundColor(.green)
                    .scaleEffect(x:maker.value, y:1.0, anchor: .leading)
            }
        }
        .frame(width: 200, height: 80)
        .onAppear(perform: {
            maker.start()
        })
    }
}

Timer.scheduledTimerとして、Double.random(in: 0 … 1)で値を出力する
@ObservedObject var maker = ValueMaker() でValueMakerを呼び出している

[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にする