【 iOS Swift 】#18 新功能提示Tool Tip, Popover

繼上一集,我們實現了讓用戶一打開APP,立馬看見這次的新功能導覽。

貼心的你,還想額外在新功能畫面,給他一些亮眼的小提示。

發現Momo購物APP的效果好漂亮,自己動手做模仿看看。

成品如下:

經過一番搜索,最後決定的作法如下:

  1. Present PopoverViewController
  2. 在Present Popover前先新增一個全螢幕的暗色背景,把要Highlight的CellView貼到暗色背景上
  3. 讓Highlight的CellView有放大縮小的動畫
  4. 當Popover Dismiss時移除暗色背景與Highlight的View

假如有更好的做法也歡迎留言分享給我喔~

  1. Present PopoverViewController

這邊不贅述PopoverViewController裡頭UI元件的擺放,直接在想顯示Popover的VC上用起來。

由於我們第二步要原地複製要Highlight的cell,在tableview的willDisplay delegate去present popover:

// 取得Cell一顯示出來的位置
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
// 指定第3個Cell
if indexPath.row == 2 {
self.showPopover(from: cell.contentView, arrowIsUp: true)
self.addHighlightEffect(to: cell.contentView)
}
}

其中,showPopover方法為:

private func showPopover(from sourceView: UIView, arrowIsUp: Bool) {

let popoverVC = PopoverViewController()
popoverVC.modalPresentationStyle = .popover

if let popover = popoverVC.popoverPresentationController {
popover.delegate = self
popover.sourceView = sourceView
// popover.passthroughViews = [sourceView]
popover.permittedArrowDirections = arrowIsUp ? .up : .down
popoverVC.arrowDirectionIsUp = arrowIsUp
popover.sourceRect = CGRect(x: sourceView.frame.midX,
y: sourceView.frame.height,
width: 0, height: 0)
}
self.present(popoverVC, animated: true, completion: nil)

popoverVC.popoverDidDismiss = {
self.removeHighlightEffect()
}
}

其實popoverPresentationController有個屬性叫做passthroughSubject也可以用,比較簡單。由於popover present時預設就會把popover身後的view變暗,其實可以直接給passthroughSubject一個裝著views的陣列,告訴他哪些不要變暗,只是我覺得這變暗的效果不夠暗🤣。

2. 在Present Popover前先新增一個全螢幕的暗色背景

/// 新增View的Highlight效果
private func addHighlightEffect(to sourceView: UIView) {
// 創建暗色背景加到全螢幕
let dimmingView = UIView(frame: UIScreen.main.bounds)
dimmingView.backgroundColor = UIColor.black.withAlphaComponent(0.75)
dimmingView.tag = 100
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }) {
keyWindow.addSubview(dimmingView)
}

// 把要Highlight的View複製一份,貼到暗色背景上
if let sourceViewSnapshot = sourceView.snapshotView(afterScreenUpdates: true) {
sourceViewSnapshot.frame = sourceView.convert(sourceView.bounds, to: dimmingView)
dimmingView.addSubview(sourceViewSnapshot)
self.animateCellView(sourceViewSnapshot)
}
}

3. 讓Highlight的CellView有放大縮小的動畫

/// 放大縮小動畫
private func animateCellView(_ sourceView: UIView) {
// 間隔0.5秒
UIView.animate(withDuration: 0.5) {
// 放大0.08倍
sourceView.transform = CGAffineTransform(scaleX: 1.08, y: 1.08)
} completion: { _ in
UIView.animate(withDuration: 0.5) {
sourceView.transform = CGAffineTransform.identity
} completion: { _ in
// 創造連續行為
self.animateCellView(sourceView)
}
}
}

4. 當Popover Dismiss時移除暗色背景與Highlight的View

extension HotSellViewController: UIPopoverPresentationControllerDelegate {
// 取得使用者點擊popover外圍的dismiss事件
func presentationControllerDidDismiss(_ presentationController: UIPresentationController) {
self.removeHighlightEffect()
}

// 不要全螢幕顯示
func adaptivePresentationStyle(for controller: UIPresentationController) -> UIModalPresentationStyle {
return .none
}
}

在UIPopoverPresentationControllerDelegate中的presentationControllerDidDismiss,可以告訴我們當使用者使用者點擊popover外圍的dismiss事件,這邊我們就去把暗色背景拿掉。

如果你的popover上有關閉按鈕,按下後執行PopoverViewController的self.dismiss(animated: true),這邊則不會觸發,要額外寫callback取得事件,我寫在上面的showPopover方法裡。

在UIPopoverPresentationControllerDelegate中,還有其他功能,像是當你發現你的popover present出來一直是全螢幕,就必須將adaptivePresentationStyle return .none。另外,假如你是不希望使用者可以點擊popover外圍來dismiss掉,只能點擊popover上的關閉按鈕,你也可以寫:

// 使用者不可點擊popover外圍來dismiss
func presentationControllerShouldDismiss(_ presentationController: UIPresentationController) -> Bool {
return false
}

最後,當收到popover的dismiss事件,這邊就把暗色背景以及複製出來的View給移除:

/// 移除View的Highlight效果
private func removeHighlightEffect() {
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let keyWindow = windowScene.windows.first(where: { $0.isKeyWindow }),
let dimmingView = keyWindow.viewWithTag(100) {
dimmingView.removeFromSuperview()
}
}

大功告成~

完整Swift Code

--

--

Yen Lin
彼得潘的 Swift iOS / Flutter App 開發教室

iOS Developer in Taiwan | A person who loves to learn cool stuffs. Check out my website to discover more about me: https://yenlin.webflow.io/