You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.07 21:55:15