[Swift] 非同期処理の並行処理

async / awaitの組み合わせで非同期処理を行う

ContentView.swift

func getWho() async -> String {
    await Task.sleep(5 * 1000 * 1000 * 1000)
    return "山本さん"
}

func getMessage() async -> String {
    await Task.sleep(3 * 1000 * 1000 * 1000)
    return "ハロー"
}

class StopWatch: ObservableObject {
    
    @Published var elapsedTime: Double = 0.0
    private var timer = Timer()
    
    func start() {
        guard !timer.isValid else { return }
        self.elapsedTime = 0.0
        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) {
            _ in
            self.elapsedTime += 0.01
        }
    }
    
    func stop() {
        timer.invalidate()
    }
}

struct ContentView: View {
    
    @State var message:String = ""
    @ObservedObject var watch = StopWatch()
    
    var body: some View {
        VStack {
            Button(action: {
                Task {
                    watch.start()
                    message = "_ _ _"
                    
                    async let who = getWho()
                    async let msg = getMessage()
                    message = await who + "," + msg + " ! "
                    watch.stop()
                }
            }) {
                Label("async TEST", systemImage: "testtube.2")
                    .background(
                        Capsule()
                            .stroke(lineWidth: 1)
                            .frame(width: 180, height: 40)
                    )
            }.padding(30)
            Text("\(message)").font(.title2)
            let milliSeconds = Int((watch.elapsedTime) * 100)%100
            let seconds = Int(watch.elapsedTime)
            Text("\(seconds).\(milliSeconds)").padding()
            Spacer()
        }
    }
}

OK, もう一声

[Swift] 画像データを非同期処理で表示

複数の画像をダウンロードできたものから表示
AsyncImage, placeholder, List, LazyVGrid, NavigationView, NavigationLink

ContentView.swift

struct Photo: Identifiable {
    var id: URL{url}
    var url: URL
}

class PhotoSource {
    var photos: [Photo] = []
    init() {
        photos = makePhotos()
    }
}

extension PhotoSource {
    func makePhotos() -> [Photo] {
        let path = "https://oshige.xsrv.jp/samples/photos/"
        let photoNames: [String] = [
            "IMG_1159.jpg", "IMG_1326.jpg", "IMG_1384.jpg", "IMG_1475.jpg",
            "IMG_1476.jpg", "IMG_1478.jpg", "IMG_1635.jpg", "IMG_1643.jpg",
            "IMG_1739.jpg", "IMG_1840.jpg", "IMG_1889.jpg", "IMG_2233.jpg",
            "IMG_2325.jpg", "IMG_2406.jpg", "IMG_2408.jpg", "IMG_4008.jpg"
        ]
        
        var photos: [Photo] = []
        for name in photoNames {
            photos.append(Photo(url: URL(string: path + name)!))
        }
        return photos
    }
}

struct ContentView: View {
    
    private var myPhotoSource = PhotoSource()
    
    var body: some View {
        NavigationView {
            List(myPhotoSource.photos) {
                photo in
                
                AsyncImage(url: photo.url) {
                    image in
                    
                    image.resizable()
                        .aspectRatio(contentMode: .fit)
                } placeholder: {
                    Color.orange
                        .overlay(Image(systemName: "photo").scaleEffect(2.0))
                }
                .mask(RoundedRectangle(cornerRadius: 16))
                .frame(height: 160)
            }
            .navigationTitle("お気に入り")
        }
    }
}

なるほど、非同期というとJSって感じだが、Swiftでもあるのね。

[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] リストのスワイプアクションを処理

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を呼び出している