美文网首页
iOS自定义圆形(4个方向)或者椭圆形(2个方向)菜单

iOS自定义圆形(4个方向)或者椭圆形(2个方向)菜单

作者: 清水_yuxin | 来源:发表于2023-07-08 01:37 被阅读0次

两种样式,支持长按1秒响应一次


image.png
image.png

1、使用

       let menu = YXOperationPanel(style: .all)
        menu.delegate = self
        view.addSubview(menu)
        let itemWH = UIScreen.main.bounds.size.width - 100
        NSLayoutConstraint.activate([
            menu.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            menu.topAnchor.constraint(equalTo: view.topAnchor, constant: 100),
            menu.widthAnchor.constraint(equalToConstant: itemWH),
            menu.heightAnchor.constraint(equalToConstant: itemWH)
        ])
   
        let menu2 = YXOperationPanel(style: .both)
        menu2.delegate = self
        view.addSubview(menu2)
        NSLayoutConstraint.activate([
            menu2.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            menu2.topAnchor.constraint(equalTo: menu.bottomAnchor, constant: 50),
            menu2.widthAnchor.constraint(equalToConstant: itemWH),
            menu2.heightAnchor.constraint(equalToConstant: itemWH/2)
        ])

2、代码

import UIKit
import Combine

protocol YXOperationPanelDelegate: AnyObject {
    func operationPanel(_ panel: YXOperationPanel, didActionWith direction: YXOperationPanel.Direction)
}
extension YXOperationPanel {
    enum Style {
        case all, both
    }
    enum Direction {
        case left, right, top, bottom
    }
}

class YXOperationPanel: UIView {
    weak var delegate: YXOperationPanelDelegate?
    private(set) var style: Style
    init(style: Style) {
        self.style = style
        super.init(frame: .zero)
        setupUI()
    }
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    let selectedBg = ShapedGradientLayer()
    lazy var leftLabel = UILabel()
    lazy var rightLabel = UILabel()
    lazy var topLabel = UILabel()
    lazy var bottomLabel = UILabel()

    override func layoutSubviews() {
        super.layoutSubviews()
        layer.cornerRadius = bounds.size.height / 2
        selectedBg.frame = CGRect(x: bounds.width/2, y: 0, width: bounds.width, height: bounds.height)
    }
    private var longPressTimer: AnyCancellable?
    
}

extension YXOperationPanel {
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let point = touches.first?.location(in: self) else { return }
        guard let d = getDirection(by: point) else { return }
        if style == .both, [Direction.top, Direction.bottom].contains(d) {
            return
        }
        updateSelectBg(with: d)
        selectedBg.isHidden = false
        startTimer(d)
    }
    
    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        guard let touchView = touches.first?.view else { return }
        if touchView.isDescendant(of: self) {
            delayHiddenSelectedBg()
        }
    }
    
    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        delayHiddenSelectedBg()
    }
    
    override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent?) {
        delayHiddenSelectedBg()
    }
    
    private func delayHiddenSelectedBg() {
        NSObject.cancelPreviousPerformRequests(withTarget: self)
        perform(#selector(hiddenSelectedBg), with: self, afterDelay: 0.1)
    }
    
    @objc private func hiddenSelectedBg() {
        selectedBg.isHidden = true
        stopTimer()
    }
    
    private func startTimer(_ direction: Direction) {
        longPressTimer = Timer.publish(every: 1, on: .main, in: .common)
            .autoconnect()
            .merge(with: Just(Date())) // 触发首次
            .sink(receiveValue: { [weak self] _ in
                guard let self = self else { return }
                self.delegate?.operationPanel(self, didActionWith: direction)
            })
    }
    
    private func stopTimer() {
        longPressTimer?.cancel()
        longPressTimer = nil
    }
    
    private func updateSelectBg(with direction: Direction) {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        switch direction {
        case .left:
            selectedBg.transform = CATransform3DMakeRotation(Double.pi, 0, 0, 1)
        case .right:
            selectedBg.transform = CATransform3DIdentity
        case .top:
            selectedBg.transform = CATransform3DMakeRotation(Double.pi*1.5, 0, 0, 1)
        case .bottom:
            selectedBg.transform = CATransform3DMakeRotation(Double.pi*0.5, 0, 0, 1)
        }
        CATransaction.commit()
    }
    
    private func getDirection(by point: CGPoint) ->Direction? {
        let x = point.x - bounds.width/2
        let y = bounds.height/2 - point.y
        if x > abs(y) {
            return .right
        }else if y > abs(x) {
            return .top
        }else if -x > abs(y) {
            return .left
        }else if -y > abs(x) {
            return .bottom
        }
        return nil
    }
        
    private func setupUI() {
        layer.borderWidth = 1
        layer.borderColor = UIColor.red.cgColor
        layer.masksToBounds = true
        backgroundColor = .clear
        translatesAutoresizingMaskIntoConstraints = false
        let padding = 50.0
        leftLabel.text = "left"
        rightLabel.text = "right"
        leftLabel.translatesAutoresizingMaskIntoConstraints = false
        rightLabel.translatesAutoresizingMaskIntoConstraints = false
        addSubview(leftLabel)
        addSubview(rightLabel)
        NSLayoutConstraint.activate([
            leftLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding),
            leftLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
            
            rightLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding),
            rightLabel.centerYAnchor.constraint(equalTo: centerYAnchor),
        ])

        if style == .all {
            topLabel.translatesAutoresizingMaskIntoConstraints = false
            bottomLabel.translatesAutoresizingMaskIntoConstraints = false
            topLabel.text = "top"
            bottomLabel.text = "bottom"
            addSubview(topLabel)
            addSubview(bottomLabel)
            NSLayoutConstraint.activate([
                topLabel.topAnchor.constraint(equalTo: topAnchor, constant: padding),
                topLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
                
                bottomLabel.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding),
                bottomLabel.centerXAnchor.constraint(equalTo: centerXAnchor),
            ])
        }
        layer.addSublayer(selectedBg)
        selectedBg.isHidden = true
    }
    
}


// 渐变扇形
extension YXOperationPanel {
    class ShapedGradientLayer: CAGradientLayer {
        var start = -(Double.pi * 0.25)
        
        override init() {
            super.init()
            anchorPoint = CGPoint(x: 0, y: 0.5)
            let startColor = UIColor.lightGray.withAlphaComponent(0.5)
            let endColor = UIColor.lightGray.withAlphaComponent(0.01)
            startPoint = .init(x: 0, y: 0.5)
            endPoint = .init(x: 1, y: 0.5)
            colors = [startColor.cgColor, endColor.cgColor, UIColor.clear.cgColor]
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        
        override func layoutSublayers() {
            super.layoutSublayers()
            
            let centetPoint = CGPoint(x: 0, y: bounds.height/2)
            let radius = bounds.width
            let end = start + Double.pi * 0.5
            
            shapePath.move(to: centetPoint)
            shapePath.addArc(withCenter: centetPoint, radius: radius, startAngle: start, endAngle: end, clockwise: true)
            shapeLayer.path = shapePath.cgPath
            mask = shapeLayer
        }
        private let shapeLayer = CAShapeLayer()
        private let shapePath = UIBezierPath()
    }
}

相关文章

  • 一行代码搞定Android弧形卫星动画菜单(附Demo)

    特性: 支持上下左右四个方向展开弧形菜单 支持扩展菜单,理论上多少个子菜单都可以 支持自定义菜单图标和子菜单图标 ...

  • 椭圆形

    宝宝,他是一个充满着无限可能性的创造体。只要你有足够耐心,他会在一个不经意的瞬间给你惊喜。 对于一个不怎么午睡的宝...

  • UIBezierPath贝塞尔曲线详细总结--官方文档(译)

    由直线和曲线线段组成的路径,可以在自定义视图中呈现这些线段。 路径可以定义简单的形状,例如矩形,椭圆形和弧形,或者...

  • 小雏菊的漂流

    山涧的泉水叮咚叮咚作响,欢快地流淌着。泉水清澈见底,底下是大块大块圆形、椭圆形、或者不规则圆形的鹅卵石。 ...

  • Android 2D绘制图形

    矩形 关键元素:左上坐标 和 右下坐标 圆形 关键元素:圆心坐标 和 半径 椭圆形或者弧形 关键元素:椭圆容器 ,...

  • 安卓中实现圆形头像

    安卓中实现圆形头像~ 大方向上讲,实现圆形图片的展示,在安卓中分 为两种大情况,一种是自定义一个view+bitm...

  • 旋转image、imageView

    iOS里面有时候图片拿出来方向不对,或者你要自己改变一个方向(旋转) UIImage*image = [UIIma...

  • 【R语言】多边形PCA图

    椭圆形 多边形

  • 王佐伊的睡莲观察日记

    睡莲:黑色,有圆形和椭圆形之分,椭圆形有个小孔,圆形没有的,我们等挖个小孔好让它发芽。 8.1号这一天,我把它放入...

  • iOS-圆形菜单JCCircularMenu

    因项目需求首页做一个圆形菜单 一级二级都是圆形,首先网上寻找了很多相似的demo,始终达不到项目要求,索性自己写一...

网友评论

      本文标题:iOS自定义圆形(4个方向)或者椭圆形(2个方向)菜单

      本文链接:https://www.haomeiwen.com/subject/wfwdudtx.html