美文网首页
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()
        }
    }
    
    

    相关文章

      网友评论

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

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