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

如何实现UITextField上浮至导航栏转为UISearchController的自定义动画?

这个需求我之前也做过,要实现类似iOS文件应用那种文本框上浮变形为搜索栏的过渡效果,核心是抛弃系统默认的present动画,改用自定义转场动画来实现。下面我给你一步步拆解实现思路和代码:

核心思路

我们需要创建一个自定义的转场动画控制器,捕捉原文本框的位置和样式,通过快照视图(snapshot)模拟文本框上浮、变形为搜索栏的过程,同时渐显目标搜索控制器的视图,完全替代系统默认的下拉动画。


第一步:实现自定义转场动画类

这个类负责处理动画的具体逻辑,包括坐标转换、快照创建、动画过渡等:

class TextFieldToSearchBarTransition: NSObject, UIViewControllerAnimatedTransitioning {
    private let originTextField: UITextField
    private let animationDuration: TimeInterval = 0.35
    
    // 初始化时传入触发动画的原文本框
    init(originTextField: UITextField) {
        self.originTextField = originTextField
        super.init()
    }
    
    // 动画时长
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return animationDuration
    }
    
    // 核心动画逻辑
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        guard let targetSearchVC = transitionContext.viewController(forKey: .to) as? UISearchController,
              let searchBar = targetSearchVC.searchBar,
              let containerView = transitionContext.containerView else {
            transitionContext.completeTransition(false)
            return
        }
        
        // 1. 转换坐标:获取原文本框的屏幕位置,以及搜索栏在容器中的最终位置
        let originFrame = originTextField.convert(originTextField.bounds, to: nil)
        let finalSearchBarFrame = containerView.convert(searchBar.bounds, from: searchBar.superview)
        
        // 2. 创建原文本框的快照,作为动画载体
        let textFieldSnapshot = originTextField.snapshotView(afterScreenUpdates: true)!
        textFieldSnapshot.frame = originFrame
        containerView.addSubview(textFieldSnapshot)
        
        // 3. 临时隐藏原文本框和搜索栏,避免干扰动画视觉
        originTextField.isHidden = true
        searchBar.isHidden = true
        
        // 4. 将搜索控制器的视图添加到容器,初始设为透明
        containerView.addSubview(targetSearchVC.view)
        targetSearchVC.view.alpha = 0
        
        // 5. 执行过渡动画:快照移动+变形,同时渐显目标视图
        UIView.animate(withDuration: animationDuration, delay: 0, options: .curveEaseInOut) {
            textFieldSnapshot.frame = finalSearchBarFrame
            textFieldSnapshot.layer.cornerRadius = searchBar.layer.cornerRadius
            textFieldSnapshot.layer.masksToBounds = true
            targetSearchVC.view.alpha = 1
        } completion: { _ in
            // 动画结束后清理:显示搜索栏,移除快照,标记动画完成
            searchBar.isHidden = false
            textFieldSnapshot.removeFromSuperview()
            transitionContext.completeTransition(true)
        }
    }
}

第二步:修改搜索控制器支持自定义转场

让PlacesSearchController遵循UIViewControllerTransitioningDelegate,提供我们的自定义动画:

class PlacesSearchController: UISearchController, UISearchBarDelegate, UIViewControllerTransitioningDelegate {
    private var customTransition: TextFieldToSearchBarTransition?
    
    // 修改便利初始化方法,传入原文本框
    convenience init(delegate: PlacesAutocompleteViewControllerDelegate, originTextField: UITextField) {
        let tableViewController = PlacesAutocompleteContainer(delegate: delegate)
        self.init(searchResultsController: tableViewController)
        
        self.searchResultsUpdater = tableViewController
        self.hidesNavigationBarDuringPresentation = false
        self.definesPresentationContext = true
        self.searchBar.placeholder = searchBarPlaceholder
        
        // 初始化自定义转场动画
        self.customTransition = TextFieldToSearchBarTransition(originTextField: originTextField)
        self.transitioningDelegate = self
    }
    
    // 提供present时的自定义动画
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return customTransition
    }
    
    // 可选:如果需要实现dismiss时的反向动画(搜索栏下沉回原文本框),可以在这里返回对应的反向动画类
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return nil
    }
}

第三步:修改触发逻辑

在ShowAddressViewController中,初始化搜索控制器时传入原文本框,触发present:

private extension ShowAddressViewController {
    @objc func streetAddressTextFieldEditingDidBegin() {
        // 初始化时传入触发动画的文本框
        let searchController = PlacesSearchController(delegate: self, originTextField: streetAddressTextField)
        // 同步原文本框的内容到搜索栏,过渡更连贯
        searchController.searchBar.text = streetAddressTextField.text
        present(searchController, animated: true, completion: nil)
    }
}

额外优化建议

  • 确保原文本框和搜索栏的样式(比如圆角、高度、字体)尽量接近,动画过渡会更自然;
  • 如果需要支持dismiss时的反向动画,可以仿照present的逻辑,创建一个反向的转场类,让搜索栏的快照下沉回原文本框位置;
  • 测试不同屏幕尺寸的适配,确保坐标转换的准确性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:28:11