自定义转换后,搜索栏从视图层次结构中删除
Posted
技术标签:
【中文标题】自定义转换后,搜索栏从视图层次结构中删除【英文标题】:Search bar gets removed from the view hierarchy after custom transition 【发布时间】:2018-05-04 14:35:07 【问题描述】:我在UINavigationController
中嵌入了两个视图控制器。第一个视图控制器将UISearchController
设置为其导航项。这是我配置搜索控制器的完整代码:
private func configureSearchController()
searchController.searchResultsUpdater = self
searchController.obscuresBackgroundDuringPresentation = false
searchController.hidesNavigationBarDuringPresentation = false
//searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.tintColor = .white
searchController.searchBar.delegate = self
//White search text
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).defaultTextAttributes = [NSAttributedStringKey.foregroundColor.rawValue: UIColor.white]
//White placeholder
UITextField.appearance(whenContainedInInstancesOf: [UISearchBar.self]).attributedPlaceholder = NSAttributedString(string: NSLocalizedString("Search", comment: "search bar placeholder"), attributes: [NSAttributedStringKey.foregroundColor: UIColor.white])
//searchController.searchBar.sizeToFit()
navigationItem.searchController = searchController
navigationItem.hidesSearchBarWhenScrolling = false
navigationController?.navigationBar.prefersLargeTitles = false
//
definesPresentationContext = true
我从viewDidLoad
调用此方法。
如问题标题中所述,我使用导航控制器自定义转换。这是过渡动画师的完整代码。
class RevealViewControllerAnimator: NSObject, UIViewControllerAnimatedTransitioning
private let animationDuration = 1.5
var operation: UINavigationControllerOperation = .push
var isShowing = true
private weak var storedContext: UIViewControllerContextTransitioning?
var snapshot: UIView?
private lazy var viewOnTopOfSnapshot: UIView? =
let view = UIView()
view.frame = self.snapshot!.frame
if isShowing
view.backgroundColor = .clear
else
view.backgroundColor = UIColor(white: 0.3, alpha: 0.4)
return view
()
private var backgroundViewBackgroundDarkColor = UIColor(white: 0.2, alpha: 0.4)
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
return animationDuration
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
storedContext = transitionContext
print ("OPERATION", operation.rawValue)
//If we are presenting a view controller
if isShowing
let fromVC = transitionContext.viewController(forKey: .from) as! ViewController1
let toVC = transitionContext.viewController(forKey: .to) as! ViewController2
snapshot = UIApplication.shared.keyWindow?.snapshotView(afterScreenUpdates: false)
let containerView = transitionContext.containerView
//Adding a view on top of a snapshot and animating its bacground color
if let snapshot = snapshot, let viewOnTopOfSnapshot = viewOnTopOfSnapshot
containerView.addSubview(self.snapshot!)
containerView.insertSubview(viewOnTopOfSnapshot, aboveSubview: snapshot)
UIView.animate(withDuration: animationDuration - 1.0, animations:
viewOnTopOfSnapshot.backgroundColor = self.backgroundViewBackgroundDarkColor
, completion: nil)
containerView.addSubview(toVC.view)
toVC.view.frame = transitionContext.finalFrame(for: toVC)
animate(toView: toVC.view, fromTriggerButton: fromVC.filterButton)
else
//If we are dismissing the view controller
let fromVC = transitionContext.viewController(forKey: .from) as! ViewController2
let toVC = transitionContext.viewController(forKey: .to) as! ViewController1
let containerView = transitionContext.containerView
//Animating the background color change to clear
if let viewOnTopOfSnapshot = viewOnTopOfSnapshot
UIView.animate(withDuration: animationDuration, animations:
viewOnTopOfSnapshot.backgroundColor = .clear
, completion: _ in
self.snapshot?.removeFromSuperview()
viewOnTopOfSnapshot.removeFromSuperview()
)
//containerView.addSubview(fromVC.view)
containerView.insertSubview(toVC.view!, belowSubview: snapshot!)
animateDismisss(fromView: fromVC.view, toTriggerButton: fromVC.saveButton)
//MARK: Animation for pushing
private func animate(toView: UIView, fromTriggerButton button: UIButton)
let rect = CGRect(x: toView.frame.maxX, y: toView.frame.minY, width: button.frame.width, height: button.frame.height)
let circleMaskPathInitial = UIBezierPath(ovalIn: rect)
let fullHeight = toView.bounds.height
let extremePoint = CGPoint(x: button.center.x, y: button.center.y - fullHeight)
let radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y))
let circleMaskPathFinal = UIBezierPath(ovalIn: button.frame.insetBy(dx: -radius - 1000, dy: -radius - 1000))
let maskLayer = CAShapeLayer()
maskLayer.path = circleMaskPathFinal.cgPath
toView.layer.mask = maskLayer
let maskLayerAnimation = CABasicAnimation(keyPath: "path")
maskLayerAnimation.fromValue = circleMaskPathInitial.cgPath
maskLayerAnimation.toValue = circleMaskPathFinal.cgPath
maskLayerAnimation.duration = animationDuration
maskLayerAnimation.delegate = self
maskLayer.add(maskLayerAnimation, forKey: "path")
//MARK: Animation for pop (dismiss)
private func animateDismisss(fromView: UIView, toTriggerButton button: UIButton)
let rect = CGRect(x: button.frame.origin.x, y: button.frame.midY, width: button.frame.width, height: button.frame.width)
let finalCircleMaskPath = UIBezierPath(ovalIn: rect)
let fullHeight = fromView.bounds.height
let extremePoint = CGPoint(x: button.center.x, y: button.center.y - fullHeight)
let radius = sqrt((extremePoint.x * extremePoint.x) + (extremePoint.y * extremePoint.y))
let initialCircleMaskPath = UIBezierPath(ovalIn: button.frame.insetBy(dx: -radius, dy: -radius))
let maskLayer = CAShapeLayer()
maskLayer.path = finalCircleMaskPath.cgPath
fromView.layer.mask = maskLayer
let maskLayerAnimation = CABasicAnimation(keyPath: "path")
maskLayerAnimation.fromValue = initialCircleMaskPath.cgPath
maskLayerAnimation.toValue = finalCircleMaskPath.cgPath
maskLayerAnimation.duration = 0.8
maskLayerAnimation.delegate = self
maskLayer.add(maskLayerAnimation, forKey: "path")
extension RevealFilterViewControllerAnimator : CAAnimationDelegate
func animationDidStop(_ anim: CAAnimation, finished flag: Bool)
if let context = storedContext
storedContext?.completeTransition(!context.transitionWasCancelled)
else
storedContext = nil
所以,简而言之,我获得了ViewController1
的快照,将其插入containerView
,然后在其顶部插入另一个视图,该视图在动画期间更改了背景颜色。弹出时,我摆脱了快照和视图,并将ViewController1
的视图插入containerView
。
正如我在问题开头提到的,我在第一个视图控制器中有一个带有搜索栏的UISearchController
。
问题是,在解除 ViewController2
之后,搜索控制器会从层次结构中删除,我得到一个空白区域。这是演示:
当我在控制台上打印UISearchController
或搜索栏时,我得到了对象信息,但是,如您所见,它从视图层次结构中消失了(在视图层次结构调试器中我找不到它) .
为什么会发生这种情况,如何解决?
【问题讨论】:
【参考方案1】:最后,我找出了导致问题的原因,一旦找到,解决方案就非常简单。所以,发生这种情况的原因是在ViewController2
的viewDidLoad
方法中我隐藏了导航栏,但在弹出视图控制器时我从未将其设置回来。
所以,这是我在第二个视图控制器中用于导航栏的代码(我的视图控制器看起来有点不同,但逻辑是相同的):
override func viewWillAppear(_ animated: Bool)
super.viewWillAppear(animated)
navigationController?.setNavigationBarHidden(true, animated: false)
override func viewWillDisappear(_ animated: Bool)
super.viewWillDisappear(animated)
navigationController?.setNavigationBarHidden(false, animated: true)
这是动画现在的样子(我知道,这里有一些粗糙的边缘,但至少,问题已经解决了)。
【讨论】:
以上是关于自定义转换后,搜索栏从视图层次结构中删除的主要内容,如果未能解决你的问题,请参考以下文章
由于奇怪的 UINavigationBar 视图层次结构,自定义 UINavigationBar 不起作用