Hacking with iOS:SwitUI Edition-潜在的な顧客リストプロジェクト(3)

7777 ワード

  • 文字列を放棄し、カプセル化とアクセス制御を使用することは、コードをより安全にするための簡単な方法であり、より良いソフトウェアを構築するための重要なステップです.

  • UserDefaultsを使用したデータの保存とロード
    このアプリケーションはほとんど実行できますが、アプリケーションを再起動すると、追加したすべてのデータが消去され、知っている人を覚えるのにあまり役に立たないという致命的な欠陥があります.この問題は、Prospects初期化プログラムがUserDefaultsからデータをロードし、データ変更時に書き戻すことで解決できます.
    今回、私たちのデータは、Prospectsクラスが@Publishedプロパティパッケージを使用しているにもかかわらず、people配列は非常に簡単で、プロトコルを追加するだけでCodableに合致しています.そのため、3つの小さな変更を行うことで、目標の大部分の方法を実現することができます.
  • は、Prospects初期化プログラムを更新し、可能な場合にはUserDefaultsからデータをロードする.
  • は、save()メソッドを同じクラスに追加し、UserDefaultsに現在のデータを書き込む.
  • は、潜在的な顧客を追加したり、isContactedの属性を切り替えたりするときに、save()を呼び出す.

  • コードがこれらの作業をすべて完了できるのを見たことがありますので、始めましょう.Prospectsに簡単な初期化プログラムを提供したので、以下に示すようにUserDefaultsを使用するように更新することができます.
    init() {
        if let data = UserDefaults.standard.data(forKey: "SavedData") {
            if let decoded = try? JSONDecoder().decode([Prospect].self, from: data) {
                self.people = decoded
                return
            }
        }
    
        self.people = []
    }
    
    save()メソッドについては、次の内容を追加します.
    func save() {
        if let encoded = try? JSONEncoder().encode(people) {
            UserDefaults.standard.set(encoded, forKey: "SavedData")
        }
    }
    

    私たちのデータは2つの場所で変更されているので、save()を呼び出して、常にデータを保存する必要があります.
    1つ目は、Prospectstoggle()メソッドであるため、以下のように修正される.
    func toggle(_ prospect: Prospect) {
        objectWillChange.send()
        prospect.isContacted.toggle()
        save()
    }
    

    2つ目は、ProspectsViewhandleScan(result:)メソッドで、リストに新しい潜在顧客を追加します.この行を見つけます.
    self.prospects.people.append(person)
    

    以下に直接追加します.
    self.prospects.save()
    

    アプリケーションを実行すると、アプリケーションを再起動しても追加した連絡先がそこに残っているので、簡単にここで停止できます.しかし、今回はさらに、他の2つの問題を解決したいと思います.
  • キー名「SavedData」を2つの場所でハードコーディングする必要があります.名前が変更されたり、より多くの場所で使用する必要がある場合は、将来再び問題が発生する可能性があります.
  • ProspectsViewで呼び出さなければならないのは、良い設計ではありません.一部の理由は、私たちのビューがモデルの内部動作原理を知るべきではないからです.また、他のビューがデータを処理している場合、save()を呼び出すのを忘れる可能性があります.

  • 最初の問題を解決するために、save()に静的プロパティを作成して保存キーを含める必要があります.したがって、文字列ではなくProspectsに対してこのプロパティを使用します.
    これをUserDefaultsクラスに追加します.
    static let saveKey = "SavedData"
    

    次に、ハードコーディングされた文字列ではなく、初期化プログラムを変更することで、次のようにすることができます.
    if let data = UserDefaults.standard.data(forKey: Self.saveKey) {
    

    保存方法は上の修正と同じで、ProspectsはここでSelfなので、Prospectsと書くのと同じ意味です
    長期的に見ると、この方法はもっと安全です.偶然「SaveKey」や「savedKey」を書くのは簡単すぎて、いろいろな間違いを導入します.Prospects.saveKeyを呼び出す問題については、save()のようなコードを記述すると、パッケージと呼ばれるソフトウェアエンジニアリングの原理を破っているというより深い問題です.これは、クラスまたは構造体で値の読み取りと書き込みが可能な外部オブジェクトの数を制限し、データの読み取り(取得)と書き込み(設定)の方法を提供することです.
    実際には、self.prospects.people.append(person)を記述する必要がなく、self.prospects.people.append(person)クラスにProspectsメソッドを作成することを意味し、add()というコードを記述することができます.結果は同じです.私たちのコードは、1人のユーザをユーザ配列に追加しますが、実装は非表示になります.これは、配列を他の位置に切り替えることができ、self.prospects.add(person)は中断しないことを意味しますが、ProspectsViewメソッドに追加の機能を追加できることを意味します.
    したがって、第2の問題を解決するために、add()Prospectsメソッドを作成し、add()を内部でトリガすることができるようにします.今すぐ追加:
    func add(_ prospect: Prospect) {
        people.append(prospect)
        save()
    }
    

    より良いことに、アクセス制御を使用してsave()配列の外部書き込みを停止することができます.これは、ビューがpeopleメソッドを使用して前景を追加する必要があることを意味します.これは、add()プロパティの定義を次のように変更することによって行われます.
    @Published private(set) var people: [Prospect]
    
    peopleの内部コードのみがProspectsメソッドを呼び出し、プライベートとしてマークすることもできます.
    private func save() {
    

    これは、偶然にエラーを犯さないようにコードをロックするのに役立ちます.コンパイラでは許可されていません.実際には、コードを構築しようとすると、save()ProspectsView配列に追加され、peopleが呼び出されます.これは許可されません.
    このエラーを解決し、コードを再度きれいにコンパイルするには、次の2行を次のコードで置き換えます.
    self.prospects.add(person)
    

    文字列を放棄し、カプセル化とアクセス制御を使用することは、コードをより安全にするための簡単な方法であり、より良いソフトウェアを構築するための重要なステップです.
    ロック画面にローカル通知を送信
    アプリケーションの最後のセクションでは、コンテキストメニューに別のボタンを追加し、ユーザーに特定のユーザーに連絡するように注意します.これにより、iOSのsave()フレームワークを使用してローカル通知が作成され、簡単なUserNotifications選択条件でコンテキストメニューに含めることができます.SwitUIは十分頭が良く、テストに合格した場合はコンテキストメニューボタンを追加できます.
    もっと面白いのは、ローカル通知をどのように手配するかです.最初の試みでは、ifを使用して、ロック画面に通知を表示する権限を明示的に要求する必要がありますが、ユーザーがいつでも考えを変更し、通知を無効にすることができるので、その後も注意してください.
    1つの選択肢は、通知を発行するたびにrequestAuthorization()が呼び出されることです.これは確かに有効です.最初にアラートが表示されますが、他のすべての場合、以前の応答に基づいてすぐに成功または失敗に戻ります.
    しかし、完了の目的で、現在のライセンス設定を要求し、通知またはライセンスを要求するかどうかを決定するために、より強力な代替案を示したいと思います.この方法を使用するのは、権限を繰り返し要求するのではなく、私たちに返された設定オブジェクトにrequestAuthorization()などの属性が含まれているためです.アラートを表示できるかどうかを確認します.ユーザーはこれに制限されている可能性があります.そのため、私たちのアイコンに角記号を表示することができます.
    したがって、alertSettingを呼び出して、通知が現在許可されているかどうかを確認します.もしそうであれば、通知を表示します.ない場合は権限を要求し、正常に戻った場合は通知も表示します.通知をスケジュールするためにコードを繰り返す必要はありません.両方の場合に呼び出すことができる閉パッケージに配置します.
    まず、このインポートをProspectsView.swiftの上部付近に追加します.
    import UserNotifications
    

    次に、この方法をgetNotificationSettings()構造体に追加します.
    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)
    
            // identifier          ,               ,                  
            let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
            center.add(request)
        }
    
        // more code to come
    }
    

    これにより、現在の潜在的な顧客に通知を作成するためのすべてのコードが閉パッケージに配置され、必要に応じて呼び出すことができます.ProspectsViewをトリガに使用しました.このトリガにより、カスタムUNCalendarNotificationTriggerインスタンスを指定できます.時間部分を9に設定します.これは、次回の午前9時にトリガーされることを意味します.
    ヒント:テストの目的で、トリガーコードを注釈して、次のコードに置き換えることをお勧めします.このコードは、これから5秒でアラートを表示します.
    let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 5, repeats: false)
    

    この方法の第2の部分については、DateComponentsおよびgetNotificationSettings()を併用して、通知が許可されたときにのみスケジュールされることを保証する.これは、上記で定義したrequestAuthorization()閉パッケージを使用します.なぜなら、私たちがすでに権限を持っている場合、または私たちが問い合わせて権限を付与されている場合、同じコードを使用することができるからです.
    置換addRequest:
    center.getNotificationSettings { settings in
        if settings.authorizationStatus == .authorized {
            addRequest()
        } else {
            center.requestAuthorization(options: [.alert, .badge, .sound]) { success, error in
                if success {
                    addRequest()
                } else {
                    print("Can't send")
                }
            }
        }
    }
    

    これは、特定の潜在的なお客様に通知をスケジュールするために必要なすべてのコードです.残りは、コンテキストメニューに追加のボタンを追加します.前のボタンの下に追加します.
    if !prospect.isContacted {
        Button("Remind Me") {
            self.addNotification(for: prospect)
        }
    }
    

    これにより、現在の手順が完了し、プロジェクトも完了しました.すぐに実行しようとすると、新しい潜在的な顧客を追加し、押さえて連絡先としてマークしたり、連絡先の注意を手配したりすることができます.
    Saving and loading data with UserDefaults Posting notifications to the lock screen