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