我有一个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
中,但为了简单起见,我在这个例子中创建了一个数组。
如果你能帮忙的话,我将不胜感激。
1条答案
按热度按时间gr8qqesn1#
我设法做到了。我在
List
级别上移动了onReceive
,并从列表中获得了所选的项目,即点击上下文菜单显示的项目。在调用后将选定项目设置为身份验证。字符串