两种样式,支持长按1秒响应一次
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()
}
}