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つ、またはそれ以上のフレームワークを組み合わせたすばらしいものを作成し、独自のカスタマイズを上に追加します。アプリをパックと区別する最後のステップであり、独自の値を追加します。