SwiftUI中Picker旁通知设置按钮无法正常点击问题
问题
我正在开发一款带有本地通知功能的待办事项列表应用。当点击两个Picker右侧的「设置通知」按钮时,按钮无法正常响应,反而会唤起第一个Picker的选择界面。我尝试使用ZStack将按钮置于Picker图层上方,但未能解决问题。使用的Xcode版本为14.2。
应用预览:
原代码如下:
import SwiftUI import UserNotifications struct ContentView: View { @State var newItem: String = "" @State var toDoList: [String] = [] @State var selectedValue1: [Int] = [] @State var selectedValue2: [Int] = [] var body: some View { VStack { //Register new to-do HStack { TextField("Please write a new task.", text: $newItem) .textFieldStyle(RoundedBorderTextFieldStyle()) .frame(width: 300) Button { toDoList.append(newItem) selectedValue1.append(-1) selectedValue2.append(-1) newItem = "" UserDefaults.standard.set(toDoList, forKey: "ToDoList") UserDefaults.standard.set(selectedValue1, forKey: "selectedValue1") UserDefaults.standard.set(selectedValue2, forKey: "selectedValue2") } label: { ZStack { RoundedRectangle(cornerRadius: 5) .frame(width: 50, height: 30) .foregroundColor(.green) Text("Add").foregroundColor(.white) } } } //List of registered tasks List { ForEach(0 ..< toDoList.count) { index in VStack{ HStack{ Text(toDoList[index]) Spacer() } todoDatePicker(index) } } } .id(UUID()) } .onAppear() { NotificationManager.instance.requestAutorization() guard let defaultItem = UserDefaults.standard.array(forKey: "ToDoList") as? [String] else { return } guard let defaultSelectedValue1 = UserDefaults.standard.array(forKey: "selectedValue1") as? [Int] else { return } guard let defaultSelectedValue2 = UserDefaults.standard.array(forKey: "selectedValue2") as? [Int] else { return } toDoList = defaultItem selectedValue1 = defaultSelectedValue1 selectedValue2 = defaultSelectedValue2 } } } //Picker to set notification time extension ContentView { func todoDatePicker(_ index: Int) -> some View { return HStack { Text("Hour") Picker("", selection: $selectedValue1[index]) { ForEach(-1 ..< 24, id: \.self) { hour in VStack{ if hour == -1 { Text("--").tag(hour) } else{ Text("\(hour)").tag(hour) } } } } Text("Minute") Picker("", selection: $selectedValue2[index]) { ForEach(-1 ..< 60, id: \.self) { minute in if minute == -1 { Text("--").tag(minute) } else { Text("\(minute)").tag(minute) } } } Button { NotificationManager.instance.scheduleNotification(todoSubtitle: toDoList[index], todoHour: selectedValue1[index], todoMinute: selectedValue2[index]) } label: { Text("Set notification") } } } } class NotificationManager { static let instance = NotificationManager() // Authorizing a notification func requestAutorization() { let options: UNAuthorizationOptions = [.alert, .sound, .badge] UNUserNotificationCenter.current().requestAuthorization(options: options) { success, error in if let error = error { print("ERROR: \(error)") } else { print("SUCCESS") } } } // Local notification at set hour/minute func scheduleNotification(todoSubtitle: String, todoHour: Int, todoMinute: Int) { let content = UNMutableNotificationContent() content.title = "Notification from To-do" content.subtitle = todoSubtitle content.sound = .default content.badge = 1 var dateComponents = DateComponents() dateComponents.hour = todoHour dateComponents.minute = todoMinute let trigger = UNCalendarNotificationTrigger(dateMatching: dateComponents, repeats: true) let request = UNNotificationRequest( identifier: UUID().uuidString, content: content, trigger: trigger) UNUserNotificationCenter.current().add(request) } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
解决方案
问题根源是SwiftUI默认样式的Picker会占据横向剩余所有空间,导致其点击区域覆盖了右侧按钮。以下两种方案可解决:
方案1:给Picker设置固定宽度
在todoDatePicker函数中,为两个Picker添加固定宽度,限制其点击区域范围:
extension ContentView { func todoDatePicker(_ index: Int) -> some View { return HStack { Text("Hour") Picker("", selection: $selectedValue1[index]) { ForEach(-1 ..< 24, id: \.self) { hour in VStack{ if hour == -1 { Text("--").tag(hour) } else{ Text("\(hour)").tag(hour) } } } } .frame(width: 60) // 限制Picker宽度 Text("Minute") Picker("", selection: $selectedValue2[index]) { ForEach(-1 ..< 60, id: \.self) { minute in if minute == -1 { Text("--").tag(minute) } else { Text("\(minute)").tag(minute) } } } .frame(width: 60) // 限制Picker宽度 Button { NotificationManager.instance.scheduleNotification(todoSubtitle: toDoList[index], todoHour: selectedValue1[index], todoMinute: selectedValue2[index]) } label: { Text("Set notification") } } } }
方案2:修改Picker为菜单样式
将Picker改为菜单样式,使其点击区域仅局限于文本部分:
extension ContentView { func todoDatePicker(_ index: Int) -> some View { return HStack { Text("Hour") Picker("", selection: $selectedValue1[index]) { ForEach(-1 ..< 24, id: \.self) { hour in VStack{ if hour == -1 { Text("--").tag(hour) } else{ Text("\(hour)").tag(hour) } } } } .pickerStyle(.menu) // 修改为菜单样式 Text("Minute") Picker("", selection: $selectedValue2[index]) { ForEach(-1 ..< 60, id: \.self) { minute in if minute == -1 { Text("--").tag(minute) } else { Text("\(minute)").tag(minute) } } } .pickerStyle(.menu) // 修改为菜单样式 Button { NotificationManager.instance.scheduleNotification(todoSubtitle: toDoList[index], todoHour: selectedValue1[index], todoMinute: selectedValue2[index]) } label: { Text("Set notification") } } } }
额外优化提示
原代码使用ForEach(0 ..< toDoList.count)遍历列表,若后续添加删除功能,可能因索引变化导致异常。建议将待办事项封装为带唯一ID的结构体并遵循Identifiable协议,让ForEach基于ID遍历,避免索引问题。
内容的提问来源于stack exchange,提问作者li day
相关产品推荐
相关产品推荐

