Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,27 @@
import UIKit

extension UIView {

/// Quick configuration to give the view shadows.
public func addShadow(offset: CGSize = .zero, opacity: Float = 0.65, radius: CGFloat = 20, color: UIColor = .black) {
public func addShadow(offset: CGSize = .zero, opacity: Float = 0.65, radius: CGFloat = 20, color: UIColor = .black)
{
layer.shadowOffset = offset
layer.shadowOpacity = opacity
layer.shadowRadius = radius
layer.shadowColor = color.cgColor
layer.masksToBounds = false
}

}

extension UIView {
var controller: UIViewController? {
var responder: UIResponder? = self
while responder != nil {
if let viewController = responder as? UIViewController {
return viewController
}
responder = responder?.next
}
return nil
}
}
26 changes: 14 additions & 12 deletions PopMenu/Classes/PopMenuAppearance.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,26 @@ public typealias Color = UIColor
/// Appearance for PopMenu.
/// Use for configuring custom styles and looks.
final public class PopMenuAppearance: NSObject {


public var popMenuActionEdgeInsets: UIEdgeInsets = .init(top: 0, left: 16, bottom: 0, right: 16)

/// Background and tint colors.
public var popMenuColor: PopMenuColor = .default()

/// Background style.
public var popMenuBackgroundStyle: PopMenuBackgroundStyle = .dimmed(color: .black, opacity: 0.4)
public var popMenuBackgroundStyle: PopMenuBackgroundStyle = .none()

/// The font for labels.
public var popMenuFont: UIFont = UIFont.systemFont(ofSize: 16, weight: .semibold)
public var popMenuFont: UIFont = UIFont.systemFont(ofSize: 14, weight: .medium)

/// Corner radius for rounded corners.
public var popMenuCornerRadius: CGFloat = 24
public var popMenuCornerRadius: CGFloat = 6

/// How tall each action is.
public var popMenuActionHeight: CGFloat = 50
public var popMenuActionHeight: CGFloat = 44

/// How many actions are the breakpoint to trigger scrollable.
public var popMenuActionCountForScrollable: UInt = 6
public var popMenuActionCountForScrollable: UInt = UInt.max

/// The scroll indicator style when the actions are scrollable.
public var popMenuScrollIndicatorStyle: UIScrollView.IndicatorStyle = .white
Expand All @@ -43,7 +45,7 @@ final public class PopMenuAppearance: NSObject {
public var popMenuScrollIndicatorHidden = false

/// The separator style for each action.
public var popMenuItemSeparator: PopMenuActionSeparator = .none()
public var popMenuItemSeparator: PopMenuActionSeparator = .fill(.black, height: 0.47)

/// The status bar style of the pop menu.
public var popMenuStatusBarStyle: UIStatusBarStyle?
Expand Down Expand Up @@ -110,7 +112,7 @@ public struct PopMenuColor {

/// Get default background and action color.
public static func `default`() -> PopMenuColor {
return PopMenuColor(backgroundColor: .gradient(fill: #colorLiteral(red: 0.168627451, green: 0.168627451, blue: 0.168627451, alpha: 1), #colorLiteral(red: 0.2156862745, green: 0.2156862745, blue: 0.2156862745, alpha: 1)), actionColor: .tint(.white))
return PopMenuColor(backgroundColor: .solid(fill: .black.withAlphaComponent(0.25)), actionColor: .tint(.white))
}

}
Expand Down
114 changes: 114 additions & 0 deletions PopMenu/Transitions/SecondaryTransitioningDelegate.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
//
// SecondaryTransitioningDelegate.swift
// Pods
//
// (\(\
// ( -.-)
// o_(")(")
// -----------------------
// Created by jeffy on 2025/4/2.
//

class SecondaryTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
static let shared = SecondaryTransitioningDelegate()

func animationController(
forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
return SecondaryPresentAnimationController()
}

func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return SecondaryDismissAnimationController()
}
}

class SecondaryPresentAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from) as? PopMenuViewController,
let toVC = transitionContext.viewController(forKey: .to) as? PopMenuViewController
else { return }
guard let expandAction = toVC.actions.first as? DidExpandAction else {
transitionContext.completeTransition(true)
return
}

let containerView = transitionContext.containerView
containerView.addSubview(toVC.view)
let finalFrame = toVC.contentFrame
// 1. 设置初始状态
let oldPosition = toVC.containerView.layer.position
if #available(iOS 16.0, *) {
toVC.containerView.anchorPoint = CGPoint(x: 0.5, y: 0)
}
toVC.contentHeightConstraint.constant = expandAction.view.frame.height
// 修改锚点后,layer的position会改变,所以需要按照动画后的 frame 重新设置
toVC.contentTopConstraint.constant = finalFrame.minY - finalFrame.height / 2
toVC.containerView.layoutIfNeeded()
expandAction.expandIcon.transform = .identity
expandAction.maskView.alpha = 0
containerView.addSubview(toVC.view)
fromVC.containerView.transform = .identity
// 2. 执行动画
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: .curveEaseInOut
) {
// 旋转图标
expandAction.expandIcon.transform = CGAffineTransform(rotationAngle: .pi / 2)
// 显示遮罩
expandAction.maskView.alpha = 1
// 缩小背景
fromVC.containerView.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
// 展开菜单
toVC.contentHeightConstraint.constant = finalFrame.height
toVC.containerView.layoutIfNeeded()
} completion: { finished in
transitionContext.completeTransition(finished)
}
}
}

class SecondaryDismissAnimationController: NSObject, UIViewControllerAnimatedTransitioning {
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromVC = transitionContext.viewController(forKey: .from) as? PopMenuViewController,
let toVC = transitionContext.viewController(forKey: .to) as? PopMenuViewController,
let expandAction = fromVC.actions.first as? DidExpandAction
else {
transitionContext.completeTransition(true)
return
}
let finalFrame = fromVC.contentFrame
// 修改锚点后,layer的position会改变,所以需要按照动画后的 frame 重新设置
fromVC.contentTopConstraint.constant = finalFrame.minY - expandAction.view.frame.height / 2
// 执行动画
toVC.containerView.layoutIfNeeded()
UIView.animate(
withDuration: transitionDuration(using: transitionContext),
delay: 0,
options: .curveEaseInOut
) {
// 1. 图标反向旋转
expandAction.expandIcon.transform = .identity
// 2. 遮罩淡出
expandAction.maskView.alpha = 0
// 还原背景
toVC.containerView.transform = .identity
// 3. 内容收缩
fromVC.contentHeightConstraint.constant = expandAction.view.frame.height
fromVC.containerView.layoutIfNeeded()
} completion: { finished in
expandAction.maskView.removeFromSuperview()
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
}
Loading