如何用UIKit实现Apple Maps悬浮按钮,解决半Sheet偏移跳变问题
实现Apple Maps风格悬浮按钮:解决半Sheet高度变化时按钮偏移跳变问题
我想用UIKit实现类似Apple Maps的悬浮操作按钮,现在卡在半Sheet高度动态变化时,按钮偏移量同步平滑变化的效果还原上。
我通过父视图控制器的sheetPresentationController展示半Sheet,重写子视图控制器的viewWillLayoutSubviews方法获取视图新高度,以此设置悬浮按钮的偏移量。但遇到问题:当用户提前松开Sheet时,按钮偏移会出现跳变(比如视图高度从200跳至500),没法像Apple Maps那样平滑过渡。请问怎么获取子视图高度的更细粒度更新?
Apple Maps中的效果演示:
我的项目中跳变效果演示:
简化示例项目代码
class ViewController: UIViewController { private var subscriptions = Set<AnyCancellable>() private var buttonBottomConstraint = NSLayoutConstraint() private lazy var floatingButton: UIButton = { let button = UIButton() button.setImage( UIImage(systemName: "list.dash"), for: .normal ) button.addTarget( self, action: #selector(openSheet), for: .touchUpInside ) button.translatesAutoresizingMaskIntoConstraints = false button.backgroundColor = .systemBackground.withAlphaComponent(0.95) button.layer.cornerRadius = 20.0 button.layer.shadowColor = UIColor.black.cgColor button.layer.shadowOpacity = 0.5 button.layer.shadowOffset = CGSize(width: 0, height: 2) button.layer.shadowRadius = 4.0 button.layer.shouldRasterize = true button.layer.rasterizationScale = UIScreen.main.scale return button }() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. configureView() } private func configureView() { view.backgroundColor = .systemMint view.addSubview(floatingButton) buttonBottomConstraint = floatingButton.bottomAnchor.constraint( equalTo: view.bottomAnchor, constant: -100 ) NSLayoutConstraint.activate([ buttonBottomConstraint, floatingButton.trailingAnchor.constraint( equalTo: view.trailingAnchor, constant: -10 ), floatingButton.heightAnchor.constraint(equalToConstant: 50), floatingButton.widthAnchor.constraint(equalToConstant: 50) ]) } @objc private func openSheet() { let childViewController = ChildViewController() configureChildViewControllerFrameSubscription(childViewController) if let sheet = childViewController.sheetPresentationController { sheet.detents = [.small(), .medium(), .large()] sheet.largestUndimmedDetentIdentifier = .medium sheet.prefersGrabberVisible = true } childViewController.isModalInPresentation = true present(childViewController, animated: true) } } extension ViewController { private func configureChildViewControllerFrameSubscription( _ childViewController: ChildViewController ) { childViewController.framePassthroughSubject .receive(on: DispatchQueue.main) .sink { frame in self.buttonBottomConstraint.isActive = false self.buttonBottomConstraint = self.floatingButton.bottomAnchor.constraint( equalTo: self.view.bottomAnchor, constant: (frame.height * -1) - 10 ) self.buttonBottomConstraint.isActive = true self.floatingButton.layoutIfNeeded() } .store(in: &subscriptions) } } extension UISheetPresentationController.Detent.Identifier { static var smallIdentifier: Self { Self("small") } } extension UISheetPresentationController.Detent { static func small() -> UISheetPresentationController.Detent { .custom(identifier: .smallIdentifier) { context in context.maximumDetentValue * 0.15 } } } class ChildViewController: UIViewController { let framePassthroughSubject = PassthroughSubject<CGRect, Never>() override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. view.backgroundColor = .systemBlue } override func viewWillLayoutSubviews() { super.viewWillLayoutSubviews() framePassthroughSubject.send(view.frame) } }
内容的提问来源于stack exchange,提问作者rizz
相关产品推荐
相关产品推荐

