refactor: rename RDReaderView -> RDEpubReaderView, update pod config and docs
- Rename source module from RDReaderView to RDEpubReaderView - Move all source files from Sources/RDReaderView/ to Sources/RDEpubReaderView/ - Update podspec: RDReaderView.podspec -> RDEpubReaderView.podspec - Update Podfile, demo project, and CocoaPods config for new pod name - Delete old RDReaderView pod support files from ReadViewDemo/Pods - Add new RDEpubReaderView pod support files - Update documentation (API ref, architecture, UML, conventions, etc.) - Add FixedLayoutRotationTests - Update .gitignore: exclude .DS_Store, manual unpack backups, _ssoft-output
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
|
||||
import UIKit
|
||||
|
||||
final class RDEPUBAttachmentTooltipView: UIView {
|
||||
|
||||
enum ArrowPlacement {
|
||||
|
||||
case top
|
||||
|
||||
case bottom
|
||||
}
|
||||
|
||||
private let contentInsets = UIEdgeInsets(top: 18, left: 20, bottom: 24, right: 20)
|
||||
|
||||
private let arrowSize = CGSize(width: 20, height: 10)
|
||||
|
||||
private let cornerRadius: CGFloat = 18
|
||||
|
||||
private(set) var minimumArrowX: CGFloat = 28
|
||||
|
||||
private var arrowTipX: CGFloat?
|
||||
|
||||
private var arrowPlacement: ArrowPlacement = .bottom
|
||||
|
||||
private let textLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.numberOfLines = 0
|
||||
label.textColor = .white
|
||||
label.font = .systemFont(ofSize: 16, weight: .regular)
|
||||
label.lineBreakMode = .byWordWrapping
|
||||
return label
|
||||
}()
|
||||
|
||||
private let shapeLayer = CAShapeLayer()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
backgroundColor = .clear
|
||||
isOpaque = false
|
||||
layer.addSublayer(shapeLayer)
|
||||
addSubview(textLabel)
|
||||
accessibilityIdentifier = "epub.reader.attachment.tooltip"
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
shapeLayer.frame = bounds
|
||||
shapeLayer.path = bubblePath(in: bounds).cgPath
|
||||
shapeLayer.fillColor = UIColor(white: 0.26, alpha: 0.96).cgColor
|
||||
|
||||
let topInset = contentInsets.top + (arrowPlacement == .top ? arrowSize.height : 0)
|
||||
let bottomInset = contentInsets.bottom + (arrowPlacement == .bottom ? arrowSize.height : 0)
|
||||
let labelFrame = bounds.inset(by: UIEdgeInsets(
|
||||
top: topInset,
|
||||
left: contentInsets.left,
|
||||
bottom: bottomInset,
|
||||
right: contentInsets.right
|
||||
))
|
||||
textLabel.frame = labelFrame
|
||||
}
|
||||
|
||||
func setArrowTipX(_ value: CGFloat, placement: ArrowPlacement) {
|
||||
arrowTipX = value
|
||||
arrowPlacement = placement
|
||||
setNeedsLayout()
|
||||
}
|
||||
|
||||
func configure(text: String, maxWidth: CGFloat) {
|
||||
textLabel.text = text
|
||||
let labelMaxWidth = max(maxWidth - contentInsets.left - contentInsets.right, 120)
|
||||
let labelSize = textLabel.sizeThatFits(CGSize(width: labelMaxWidth, height: .greatestFiniteMagnitude))
|
||||
frame.size = CGSize(
|
||||
width: min(maxWidth, labelSize.width + contentInsets.left + contentInsets.right),
|
||||
height: labelSize.height + contentInsets.top + contentInsets.bottom + arrowSize.height
|
||||
)
|
||||
setNeedsLayout()
|
||||
layoutIfNeeded()
|
||||
}
|
||||
|
||||
private func bubblePath(in rect: CGRect) -> UIBezierPath {
|
||||
let bubbleRect: CGRect
|
||||
switch arrowPlacement {
|
||||
case .bottom:
|
||||
bubbleRect = CGRect(
|
||||
x: rect.minX,
|
||||
y: rect.minY,
|
||||
width: rect.width,
|
||||
height: rect.height - arrowSize.height
|
||||
)
|
||||
case .top:
|
||||
bubbleRect = CGRect(
|
||||
x: rect.minX,
|
||||
y: rect.minY + arrowSize.height,
|
||||
width: rect.width,
|
||||
height: rect.height - arrowSize.height
|
||||
)
|
||||
}
|
||||
let arrowMidX = min(
|
||||
max(arrowTipX ?? bubbleRect.midX, minimumArrowX),
|
||||
bubbleRect.width - minimumArrowX
|
||||
)
|
||||
let arrowHalfWidth = arrowSize.width / 2
|
||||
|
||||
let path = UIBezierPath()
|
||||
switch arrowPlacement {
|
||||
case .bottom:
|
||||
path.move(to: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.minY))
|
||||
path.addLine(to: CGPoint(x: bubbleRect.maxX - cornerRadius, y: bubbleRect.minY))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.maxX - cornerRadius, y: bubbleRect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: -.pi / 2,
|
||||
endAngle: 0,
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: bubbleRect.maxX, y: bubbleRect.maxY - cornerRadius))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.maxX - cornerRadius, y: bubbleRect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: .pi / 2,
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: arrowMidX + arrowHalfWidth, y: bubbleRect.maxY))
|
||||
path.addLine(to: CGPoint(x: arrowMidX, y: bubbleRect.maxY + arrowSize.height))
|
||||
path.addLine(to: CGPoint(x: arrowMidX - arrowHalfWidth, y: bubbleRect.maxY))
|
||||
path.addLine(to: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.maxY))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi / 2,
|
||||
endAngle: .pi,
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: bubbleRect.minX, y: bubbleRect.minY + cornerRadius))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi,
|
||||
endAngle: -.pi / 2,
|
||||
clockwise: true
|
||||
)
|
||||
case .top:
|
||||
path.move(to: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.minY))
|
||||
path.addLine(to: CGPoint(x: arrowMidX - arrowHalfWidth, y: bubbleRect.minY))
|
||||
path.addLine(to: CGPoint(x: arrowMidX, y: bubbleRect.minY - arrowSize.height))
|
||||
path.addLine(to: CGPoint(x: arrowMidX + arrowHalfWidth, y: bubbleRect.minY))
|
||||
path.addLine(to: CGPoint(x: bubbleRect.maxX - cornerRadius, y: bubbleRect.minY))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.maxX - cornerRadius, y: bubbleRect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: -.pi / 2,
|
||||
endAngle: 0,
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: bubbleRect.maxX, y: bubbleRect.maxY - cornerRadius))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.maxX - cornerRadius, y: bubbleRect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: 0,
|
||||
endAngle: .pi / 2,
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.maxY))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.maxY - cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi / 2,
|
||||
endAngle: .pi,
|
||||
clockwise: true
|
||||
)
|
||||
path.addLine(to: CGPoint(x: bubbleRect.minX, y: bubbleRect.minY + cornerRadius))
|
||||
path.addArc(
|
||||
withCenter: CGPoint(x: bubbleRect.minX + cornerRadius, y: bubbleRect.minY + cornerRadius),
|
||||
radius: cornerRadius,
|
||||
startAngle: .pi,
|
||||
endAngle: -.pi / 2,
|
||||
clockwise: true
|
||||
)
|
||||
}
|
||||
path.close()
|
||||
return path
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user