iOS自定義圓形(4個方向)或者橢圓形(2個方向)菜單

兩種樣式探赫,支持長按1秒響應(yīng)一次


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())) // 觸發(fā)首次
            .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()
    }
}

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末吝梅,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子惹骂,更是在濱河造成了極大的恐慌苏携,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,406評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件对粪,死亡現(xiàn)場離奇詭異右冻,居然都是意外死亡,警方通過查閱死者的電腦和手機著拭,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,732評論 3 393
  • 文/潘曉璐 我一進(jìn)店門纱扭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人儡遮,你說我怎么就攤上這事乳蛾。” “怎么了鄙币?”我有些...
    開封第一講書人閱讀 163,711評論 0 353
  • 文/不壞的土叔 我叫張陵肃叶,是天一觀的道長。 經(jīng)常有香客問我十嘿,道長因惭,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,380評論 1 293
  • 正文 為了忘掉前任详幽,我火速辦了婚禮筛欢,結(jié)果婚禮上浸锨,老公的妹妹穿的比我還像新娘。我一直安慰自己版姑,他們只是感情好柱搜,可當(dāng)我...
    茶點故事閱讀 67,432評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著剥险,像睡著了一般聪蘸。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上表制,一...
    開封第一講書人閱讀 51,301評論 1 301
  • 那天健爬,我揣著相機與錄音,去河邊找鬼么介。 笑死娜遵,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的壤短。 我是一名探鬼主播设拟,決...
    沈念sama閱讀 40,145評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼久脯!你這毒婦竟也來了纳胧?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,008評論 0 276
  • 序言:老撾萬榮一對情侶失蹤帘撰,失蹤者是張志新(化名)和其女友劉穎跑慕,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體摧找,經(jīng)...
    沈念sama閱讀 45,443評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡核行,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,649評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了慰于。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片钮科。...
    茶點故事閱讀 39,795評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖婆赠,靈堂內(nèi)的尸體忽然破棺而出绵脯,到底是詐尸還是另有隱情,我是刑警寧澤休里,帶...
    沈念sama閱讀 35,501評論 5 345
  • 正文 年R本政府宣布蛆挫,位于F島的核電站,受9級特大地震影響妙黍,放射性物質(zhì)發(fā)生泄漏悴侵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,119評論 3 328
  • 文/蒙蒙 一拭嫁、第九天 我趴在偏房一處隱蔽的房頂上張望可免。 院中可真熱鬧抓于,春花似錦、人聲如沸浇借。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,731評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妇垢。三九已至巾遭,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間闯估,已是汗流浹背灼舍。 一陣腳步聲響...
    開封第一講書人閱讀 32,865評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留涨薪,地道東北人骑素。 一個月前我還...
    沈念sama閱讀 47,899評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像刚夺,于是被迫代替她去往敵國和親砂豌。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,724評論 2 354

推薦閱讀更多精彩內(nèi)容