100 Days of SwiftUI(DAY 84(Project16 part 6))

UserDefaultsによるデータの保存と読み込み
Saving and loading data with UserDefaults

//
//  Prospect.swift
//  HotProspects
//
//  Created by Naoki Abe on 2020/09/01.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import SwiftUI

class Prospect: Identifiable, Codable {
    let id = UUID()
    var name = "Anonymous"
    var emailAddress = ""
    //var isContacted = false
    fileprivate(set) var isContacted = false
}

class Prospects: ObservableObject {
    static let saveKey = "SavedData"
    //@Published var people: [Prospect]
    @Published private(set) var people: [Prospect]

    init() {
        if let data = UserDefaults.standard.data(forKey: Self.saveKey) {
            if let decoded = try? JSONDecoder().decode([Prospect].self, from: data) {
                self.people = decoded
                return
            }
        }

        self.people = []
    }
    
    private func save() {
        if let encoded = try? JSONEncoder().encode(people) {
            UserDefaults.standard.set(encoded, forKey: Self.saveKey)
        }
    }
    
    func add(_ prospect: Prospect) {
        people.append(prospect)
        save()
    }
    
    func toggle(_ prospect: Prospect) {
        objectWillChange.send()
        prospect.isContacted.toggle()
        save()
    }
}

//
//  ProspectsView.swift
//  HotProspects
//
//  Created by Naoki Abe on 2020/09/01.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import SwiftUI
import CodeScanner

struct ProspectsView: View {
    @EnvironmentObject var prospects: Prospects
    
    @State private var isShowingScanner = false
    
    enum FilterType {
        case none, contacted, uncontacted
    }
    
    let filter: FilterType
    
    var title: String {
        switch filter {
        case .none:
            return "Everyone"
        case .contacted:
            return "Contacted people"
        case .uncontacted:
            return "Uncontacted people"
        }
    }
    
    var filteredProspects: [Prospect] {
        switch filter {
        case .none:
            return prospects.people
        case .contacted:
            return prospects.people.filter { $0.isContacted }
        case .uncontacted:
            return prospects.people.filter { !$0.isContacted }
        }
    }
    
    func handleScan(result: Result<String, CodeScannerView.ScanError>) {
       self.isShowingScanner = false
       // more code to come
        switch result {
        case .success(let code):
            let details = code.components(separatedBy: "\n")
            guard details.count == 2 else { return }

            let person = Prospect()
            person.name = details[0]
            person.emailAddress = details[1]

            //self.prospects.people.append(person)
            //self.prospects.save()
            self.prospects.add(person)
            
        case .failure(let error):
            print("Scanning failed")
        }
    }
    
    var body: some View {
        NavigationView {
                // Text("People: \(prospects.people.count)")
                List {
                    ForEach(filteredProspects) { prospect in
                        VStack(alignment: .leading) {
                            Text(prospect.name)
                                .font(.headline)
                            Text(prospect.emailAddress)
                                .foregroundColor(.secondary)
                        }
                        .contextMenu {
                            Button(prospect.isContacted ? "Mark Uncontacted" : "Mark Contacted" ) {
                                //prospect.isContacted.toggle()
                                self.prospects.toggle(prospect)
                            }
                        }
                    }
                }
                .navigationBarTitle(title)
                .navigationBarItems(trailing: Button(action: {
                    self.isShowingScanner = true
//                    let prospect = Prospect()
//                    prospect.name = "Paul Hudson"
//                    prospect.emailAddress = "paul@hackingwithswift.com"
//                    self.prospects.people.append(prospect)
                }) {
                    Image(systemName: "qrcode.viewfinder")
                    Text("Scan")
                })
                .sheet(isPresented: $isShowingScanner) {
                    CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: self.handleScan)
                }
        }
    }
}

ロック画面への通知の投稿
Posting notifications to the lock screen
iOSのUserNotificationsフレームワークを使用してローカル通知を作成し、条件付きで簡単なifチェックを使用してコンテキストメニューに含めます。


//
//  ProspectsView.swift
//  HotProspects
//
//  Created by Naoki Abe on 2020/09/01.
//  Copyright © 2020 Naoki Abe. All rights reserved.
//

import SwiftUI
import CodeScanner
import UserNotifications

struct ProspectsView: View {
    @EnvironmentObject var prospects: Prospects
    
    @State private var isShowingScanner = false
    
    enum FilterType {
        case none, contacted, uncontacted
    }
    
    let filter: FilterType
    
    var title: String {
        switch filter {
        case .none:
            return "Everyone"
        case .contacted:
            return "Contacted people"
        case .uncontacted:
            return "Uncontacted people"
        }
    }
    
    var filteredProspects: [Prospect] {
        switch filter {
        case .none:
            return prospects.people
        case .contacted:
            return prospects.people.filter { $0.isContacted }
        case .uncontacted:
            return prospects.people.filter { !$0.isContacted }
        }
    }
    
    func handleScan(result: Result<String, CodeScannerView.ScanError>) {
       self.isShowingScanner = false
       // more code to come
        switch result {
        case .success(let code):
            let details = code.components(separatedBy: "\n")
            guard details.count == 2 else { return }

            let person = Prospect()
            person.name = details[0]
            person.emailAddress = details[1]

            //self.prospects.people.append(person)
            //self.prospects.save()
            self.prospects.add(person)
            
        case .failure(let error):
            print("Scanning failed")
        }
    }
    
    func addNotification(for prospect: Prospect) {
        let center = UNUserNotificationCenter.current()

        let addRequest = {
            let content = UNMutableNotificationContent()
            content.title = "Contact \(prospect.name)"
            content.subtitle = prospect.emailAddress
            content.sound = UNNotificationSound.default

            var dateComponents = DateComponents()
            dateComponents.hour = 9
            //let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: false)
            let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)

            let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
            center.add(request)
        }

        // more code to come
        //現在の承認設定を要求し、それを使用して、通知をスケジュールするか、許可を要求するかを決定できます
        center.getNotificationSettings { settings in
            if settings.authorizationStatus == .authorized {
                addRequest()
            } else {
                center.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
                    if success {
                        addRequest()
                    } else {
                        print("D'oh")
                    }
                }
            }
        }
    }
    
    var body: some View {
        NavigationView {
                // Text("People: \(prospects.people.count)")
                List {
                    ForEach(filteredProspects) { prospect in
                        VStack(alignment: .leading) {
                            Text(prospect.name)
                                .font(.headline)
                            Text(prospect.emailAddress)
                                .foregroundColor(.secondary)
                        }
                        .contextMenu {
                            Button(prospect.isContacted ? "Mark Uncontacted" : "Mark Contacted" ) {
                                //prospect.isContacted.toggle()
                                self.prospects.toggle(prospect)
                            }
                            if !prospect.isContacted {
                                Button("Remind Me") {
                                    self.addNotification(for: prospect)
                                }
                            }
                        }
                    }
                }
                .navigationBarTitle(title)
                .navigationBarItems(trailing: Button(action: {
                    self.isShowingScanner = true
//                    let prospect = Prospect()
//                    prospect.name = "Paul Hudson"
//                    prospect.emailAddress = "paul@hackingwithswift.com"
//                    self.prospects.people.append(prospect)
                }) {
                    Image(systemName: "qrcode.viewfinder")
                    Text("Scan")
                })
                .sheet(isPresented: $isShowingScanner) {
                    CodeScannerView(codeTypes: [.qr], simulatedData: "Paul Hudson\npaul@hackingwithswift.com", completion: self.handleScan)
                }
        }
    }
}

これは私たちにとって最大のプロジェクトでしたが、最終結果は、実際の会議の開始点を簡単に形成できる、もう1つの本当に便利なアプリです。我々はまた、カスタム環境オブジェクトについて学んだ道に沿って、TabView、Result、objectWillChange、画像補間、コンテキストメニュー、ローカル通知、スウィフトのパッケージの依存関係、filter()およびmap()、およびそんなに多く-それがパックされています!

これまでに、Appleの他のフレームワークのいくつか(Core ML、MapKit、Core Image、および現在はUserNotifications)を探索してきました。したがって、Appleがすでに行ったすべての作業に依存するだけで、どれだけの量を構築できるかを理解していただければ幸いです。私たちのために。
イギリスの数学者アイザック・ニュートンはかつて言った、「私がさらに見た場合、それは巨人の肩の上に立つことによるものです。」これは、これまでで最も影響力のある科学者の1人であるとはかなり控えめな見方です。

AppleのAPIを操作する場合も同様です。自分でCreate MLを書いてもらえますか?またはUIKit?それともMapKit、またはCore Image、またはUserNotifications?たぶんそのうちの1つ、そして私がそれらのうちの2つに多くの助けがあったなら、おそらくそれはかなりありそうにありません。

幸いなことに、私はそうする必要はありませんし、あなたもそうではありません。Appleの膨大なAPIのコレクションは、私たちも巨人の肩の上に立っていることを意味します。日付を適切に処理するなどの作業も膨大な作業ですが、Appleがすでに解決してくれているので、心配する必要はありません。

だから、この素晴らしい機会をつかんでください!2つ、3つ、またはそれ以上のフレームワークを組み合わせたすばらしいものを作成し、独自のカスタマイズを上に追加します。アプリをパックと区別する最後のステップであり、独自の値を追加します。