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

如何实现Apple Reminders同款搜索图标布局与交互效果?

实现Apple Reminders风格的搜索功能(SwiftUI)

核心实现思路

要完全匹配Reminders的搜索交互,需要借助UIKit原生组件的特性来修正SwiftUI默认搜索栏的不足,核心解决方向:

  • 用UISearchController的原生行为让搜索框附着在键盘顶部
  • 自定义搜索栏布局,替换默认内部关闭按钮为独立外部按钮
  • 通过状态绑定实现点击关闭按钮或收起键盘时一步隐藏搜索框

完整代码实现

import SwiftUI
import UIKit

// 包装UIKit搜索控制器,适配SwiftUI
struct ReminderSearchController: UIViewControllerRepresentable {
    @Binding var isSearchActive: Bool
    @Binding var searchText: String
    let onCancel: () -> Void
    
    func makeUIViewController(context: Context) -> UISearchController {
        let searchController = UISearchController(searchResultsController: nil)
        searchController.searchBar.delegate = context.coordinator
        searchController.hidesNavigationBarDuringPresentation = false
        searchController.obscuresBackgroundDuringPresentation = false
        
        // 隐藏默认内部取消按钮,添加独立外部按钮
        let cancelBtn = UIButton(type: .system)
        cancelBtn.setTitle("取消", for: .normal)
        cancelBtn.addTarget(context.coordinator, action: #selector(Coordinator.handleCancel), for: .touchUpInside)
        
        searchController.searchBar.showsCancelButton = false
        searchController.searchBar.addSubview(cancelBtn)
        
        // 布局取消按钮到搜索栏右侧
        cancelBtn.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            cancelBtn.centerYAnchor.constraint(equalTo: searchController.searchBar.centerYAnchor),
            cancelBtn.trailingAnchor.constraint(equalTo: searchController.searchBar.trailingAnchor, constant: -16)
        ])
        
        return searchController
    }
    
    func updateUIViewController(_ uiViewController: UISearchController, context: Context) {
        uiViewController.isActive = isSearchActive
        uiViewController.searchBar.text = searchText
    }
    
    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }
    
    class Coordinator: NSObject, UISearchBarDelegate {
        let parent: ReminderSearchController
        
        init(parent: ReminderSearchController) {
            self.parent = parent
        }
        
        @objc func handleCancel() {
            parent.searchText = ""
            parent.isSearchActive = false
            parent.onCancel()
        }
        
        func searchBarTextDidEndEditing(_ searchBar: UISearchBar) {
            // 收起键盘时自动关闭搜索框
            parent.isSearchActive = false
        }
    }
}

// 主界面视图
struct ReminderMainView: View {
    @State private var isSearchActive = false
    @State private var searchText = ""
    @State private var reminderItems = ["买菜", "整理桌面", "完成代码优化"]
    
    var body: some View {
        NavigationStack {
            List(filteredItems) { item in
                Text(item)
            }
            .navigationTitle("提醒事项")
            .navigationBarItems(
                leading: Button(action: { /* 设置按钮逻辑 */ }) {
                    Image(systemName: "gear")
                },
                trailing: Button(action: { isSearchActive = true }) {
                    Image(systemName: "magnifyingglass")
                }
            )
            // 嵌入自定义搜索控制器
            .overlay(
                ReminderSearchController(
                    isSearchActive: $isSearchActive,
                    searchText: $searchText,
                    onCancel: { /* 取消搜索后的额外逻辑 */ }
                )
                .frame(height: 0) // 隐藏默认占位高度
            )
            .toolbar {
                ToolbarItem(placement: .bottomBar) {
                    Button(action: { /* 添加新提醒逻辑 */ }) {
                        Image(systemName: "plus.circle.fill")
                            .font(.title)
                    }
                }
            }
        }
    }
    
    // 搜索过滤逻辑
    private var filteredItems: [String] {
        guard !searchText.isEmpty else { return reminderItems }
        return reminderItems.filter { $0.localizedCaseInsensitiveContains(searchText) }
    }
}

#Preview {
    ReminderMainView()
}

问题针对性解决说明

  1. 搜索框附着键盘顶部:借助UISearchController的原生交互,它会自动在激活时附着于键盘上方,替代SwiftUI默认的顶部搜索栏布局。
  2. 独立关闭按钮:隐藏搜索栏默认的内部取消按钮,手动添加独立的UIButton并布局到搜索栏右侧,点击后直接触发关闭逻辑。
  3. 一步关闭操作:通过isSearchActive状态绑定,点击关闭按钮时同时清空搜索文本并设置isSearchActive = false;同时监听搜索栏结束编辑事件,收起键盘时自动关闭搜索框,实现一步完成关闭。

额外优化建议

  • 可以给搜索栏添加圆角、阴影,更贴近Reminders的视觉风格
  • 搜索过滤逻辑可添加防抖处理,避免实时输入频繁触发过滤操作

内容的提问来源于stack exchange,提问作者Edward

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.11 13:04:51