SwiftUI中匹配Face ID后,如何更新列表中选中的行?

oxf4rvwz  于 2023-08-02  发布在  Swift
关注(0)|答案(1)|浏览(82)

我有一个List,我想更新项目的文本时,contextMenu按钮是轻敲。
点击按钮时,@Published值将更新。我用onReceive监听值的变化,如果该值为真,则我长时间按下contextMenu并点击按钮的列表项应该更新其文本。
问题是列表中的所有项目都已更新。所以onReceive对列表中的每个元素都是命中的。在某种程度上我理解,因为元素填充在ForEach中,尽管我的期望是只更新一个项。
我试图复制的行为是从Notes应用程序,当你长按Note并点击Lock Note。在该操作中,锁定仅应用于所选注解。
我尝试捕获所选索引,但列表中的每个项目都再次触发onReceive
如何定义一个自定义修饰符,比如onDelete,它可以删除右边的IndexSet,或者定义一个函数,它可以接受IndexSet并将我需要的更改应用到该索引?
下面是我正在尝试解决的代码。

import SwiftUI
import LocalAuthentication

enum BiometricStates {
    case available
    case lockedOut
    case notAvailable
    case unknown
}

class BiometricsHandler: ObservableObject {
    @Published var biometricsAvailable = false
    @Published var isUnlocked = false

    private var context = LAContext()

    private var biometryState = BiometricStates.unknown {
        didSet {
            switch biometryState {
            case .available:
                self.biometricsAvailable = true
            case .lockedOut:
//                self.loginState = .biometryLockout
                self.biometricsAvailable = false
            case .notAvailable, .unknown:
                self.biometricsAvailable = false
            }
        }
    }

    init() {
//        self.loginState = .loggedOut
        checkBiometrics()
    }

    private func checkBiometrics() {
        var evaluationError: NSError?
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &evaluationError) {
            switch context.biometryType {
            case .faceID, .touchID:
                biometryState = .available
            default:
                biometryState = .unknown
            }
        } else {
            guard let error = evaluationError else {
                biometryState = .unknown
                return
            }
        
            let errorCode = LAError(_nsError: error).code
        
            switch(errorCode) {
            case .biometryNotEnrolled, .biometryNotAvailable:
                biometryState = .notAvailable
            case .biometryLockout:
                biometryState = .lockedOut
            default:
                biometryState = .unknown
            }
        }
    }

    func authenticate() {
        let context = LAContext()
        var error: NSError?
    
        // check wether biometric authentication is possible
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            // it's possible, so go ahead and use it
            let reason = "We need to unlock your data"
        
            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                // authentication has now completed
                if success {
                    // authenticated successfully
                    Task { @MainActor in
                        self.isUnlocked = true
                    }
                }
                else {
                    // there was a problem
                }
            }
        }
        else {
            // no biometrics
        }
    }
}

struct Ocean: Identifiable, Equatable {
    let name: String
    let id = UUID()
    var hasPhoto: Bool = false
}

struct OceanDetails: View {
    var ocean: Ocean

    var body: some View {
        Text("\(ocean.name)")
    }
}

struct ContentView: View {
    @EnvironmentObject var biometricsHandler: BiometricsHandler

    @State private var oceans = [
        Ocean(name: "Pacific"),
        Ocean(name: "Atlantic"),
        Ocean(name: "Indian"),
        Ocean(name: "Southern"),
        Ocean(name: "Arctic")
    ]

    var body: some View {
        NavigationView {
            List {
                ForEach(Array(oceans.enumerated()), id: \.element.id) { (index,ocean) in
                    NavigationLink(destination: OceanDetails(ocean: ocean)) {
                        ocean.hasPhoto ? Text(ocean.name) + Text(Image(systemName: "lock")) : Text("\(ocean.name)")
                    }
                    .contextMenu() {
                        Button(action: {
                            biometricsHandler.authenticate()
                        }) {
                            if ocean.hasPhoto {
                                Label("Remove lock", systemImage: "lock.slash")
                            } else {
                                Label("Lock", systemImage: "lock")
                            }
                        }
                    }
                    .onReceive(biometricsHandler.$isUnlocked) { isUnlocked in
                        if isUnlocked {
                            oceans[index].hasPhoto.toggle()
                            biometricsHandler.isUnlocked = false
                        }
                    }
                }
                .onDelete(perform: removeRows)
            }
        }
    }

    func removeRows(at offsets: IndexSet) {
        withAnimation {
            oceans.remove(atOffsets: offsets)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(BiometricsHandler())
    }
}

字符串
这只是我的应用程序的复制。我想了解这个onReceive是如何工作的,或者在ForEach上应用它是否是一个好主意。我试图将它移动到List级别,但我无法再访问从循环中获得的索引。
还想提一下,在真实的应用程序中,数据被持久化在CoreData中,但为了简单起见,我在这个例子中创建了一个数组。
如果你能帮忙的话,我将不胜感激。

gr8qqesn

gr8qqesn1#

我设法做到了。我在List级别上移动了onReceive,并从列表中获得了所选的项目,即点击上下文菜单显示的项目。在调用后将选定项目设置为身份验证。

import SwiftUI
import LocalAuthentication

enum BiometricStates {
    case available
    case lockedOut
    case notAvailable
    case unknown
}

class BiometricsHandler: ObservableObject {
    @Published var biometricsAvailable = false
    @Published var isUnlocked = false

    private var context = LAContext()

    private var biometryState = BiometricStates.unknown {
        didSet {
            switch biometryState {
            case .available:
                self.biometricsAvailable = true
            case .lockedOut:
//                self.loginState = .biometryLockout
                self.biometricsAvailable = false
            case .notAvailable, .unknown:
                self.biometricsAvailable = false
            }
        }
    }

    init() {
//        self.loginState = .loggedOut
        checkBiometrics()
    }

    private func checkBiometrics() {
        var evaluationError: NSError?
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &evaluationError) {
            switch context.biometryType {
            case .faceID, .touchID:
                biometryState = .available
            default:
                biometryState = .unknown
            }
        } else {
            guard let error = evaluationError else {
                biometryState = .unknown
                return
            }
        
            let errorCode = LAError(_nsError: error).code
        
            switch(errorCode) {
            case .biometryNotEnrolled, .biometryNotAvailable:
                biometryState = .notAvailable
            case .biometryLockout:
                biometryState = .lockedOut
            default:
                biometryState = .unknown
            }
        }
    }

    func authenticate() {
        let context = LAContext()
        var error: NSError?
    
        // check wether biometric authentication is possible
        if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: &error) {
            // it's possible, so go ahead and use it
            let reason = "We need to unlock your data"
        
            context.evaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, localizedReason: reason) { success, authenticationError in
                // authentication has now completed
                if success {
                    // authenticated successfully
                    Task { @MainActor in
                        self.isUnlocked = true
                    }
                }
                else {
                    // there was a problem
                }
            }
        }
        else {
            // no biometrics
        }
    }

    func passcodeAuthenticate() {
        let context = LAContext()
        var error: NSError?
    
        // check wether biometric authentication is possible
        if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
            // it's possible, so go ahead and use it
            let reason = "Authenticate to access your data"
        
            context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { success, authenticationError in
                // authentication has now completed
                if success {
                    // authenticated successfully
                    DispatchQueue.main.async {
                        self.isUnlocked = true
                    }
                }
                else {
                    // there was a problem
                }
            }
        }
        else {
            // no biometrics
        }

    }
}

struct Ocean: Identifiable, Equatable {
    let name: String
    let id = UUID()
    var hasPhoto: Bool = false
}

struct OceanDetails: View {
    var ocean: Ocean

    var body: some View {
        Text("\(ocean.name)")
    }
}

struct ContentView: View {
    @EnvironmentObject var biometricsHandler: BiometricsHandler

    @State private var oceans = [
        Ocean(name: "Pacific"),
        Ocean(name: "Atlantic"),
        Ocean(name: "Indian"),
        Ocean(name: "Southern"),
        Ocean(name: "Arctic")
    ]
    @State var selectedOcean: Ocean?
    @State var selectedIndex: Int?
    @State var biometricsCalls: Int = 0

    var body: some View {
        NavigationView {
            List {
                ForEach(Array(oceans.enumerated()), id: \.element.id) { (index,ocean) in
                    NavigationLink(destination: OceanDetails(ocean: ocean)) {
                        ocean.hasPhoto ? Text(ocean.name) + Text(Image(systemName: "lock")) : Text("\(ocean.name)")
                    }
                    .contextMenu() {
                        Button(action: {
                            biometricsHandler.authenticate()
                            if biometricsHandler.isUnlocked {
                                biometricsHandler.passcodeAuthenticate()
                            }
                            selectedOcean = ocean
                        }) {
                            if ocean.hasPhoto {
                                Label("Remove lock", systemImage: "lock.slash")
                            } else {
                                Label("Lock", systemImage: "lock")
                            }
                        }
                    }
                }
                .onDelete(perform: removeRows)
            }
            .onReceive(biometricsHandler.$isUnlocked) { isUnlocked in
                if isUnlocked {
                    if let index = oceans.firstIndex(where: {$0 == selectedOcean}) {
                        oceans[index].hasPhoto.toggle()
                    }
                }
            }
        }
    }

    func removeRows(at offsets: IndexSet) {
        withAnimation {
            oceans.remove(atOffsets: offsets)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
            .environmentObject(BiometricsHandler())
    }
}

字符串

相关问题