chat setting font size

This commit is contained in:
mh 2025-11-04 10:44:50 +08:00
parent b893526abe
commit 04b2131efc
1 changed files with 197 additions and 55 deletions

View File

@ -84,18 +84,19 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
slider.maximumValue = Float(maxFontSize)
slider.value = Float(currentFontSize)
// track
slider.minimumTrackTintColor = UIColor(hex: "#E0E0E0")
slider.maximumTrackTintColor = UIColor(hex: "#E0E0E0")
// track
slider.minimumTrackTintColor = .clear
slider.maximumTrackTintColor = .clear
// thumb
let thumbSize: CGFloat = 24
let thumbSize: CGFloat = 20
UIGraphicsBeginImageContextWithOptions(CGSize(width: thumbSize, height: thumbSize), false, 0.0)
if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(x: 0, y: 0, width: thumbSize, height: thumbSize))
// 使thumb
context.setShadow(offset: CGSize(width: 0, height: 1), blur: 2, color: UIColor.black.withAlphaComponent(0.2).cgColor)
//
context.setShadow(offset: CGSize(width: 0, height: 1), blur: 2, color: UIColor.black.withAlphaComponent(0.15).cgColor)
context.fillEllipse(in: CGRect(x: 0, y: 0, width: thumbSize, height: thumbSize))
}
let thumbImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
@ -103,7 +104,7 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
slider.setThumbImage(thumbImage, for: .normal)
slider.setThumbImage(thumbImage, for: .highlighted)
//
//
slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
slider.addTarget(self, action: #selector(sliderTouchDown(_:)), for: .touchDown)
slider.addTarget(self, action: #selector(sliderTouchUp(_:)), for: [.touchUpInside, .touchUpOutside])
@ -111,13 +112,35 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
return slider
}()
private lazy var tickMarksContainer: UIView = {
// 线线
private lazy var customTrackView: UIView = {
let view = UIView()
view.backgroundColor = .clear
view.isUserInteractionEnabled = false //
return view
}()
// 线 5pt
private lazy var trackLine: UIView = {
let view = UIView()
view.backgroundColor = UIColor(hex: "#A4A8B7")
return view
}()
// 5 线 5*11
private var tickMarks: [UIView] = []
private var isTickMarksCreated = false
private var isDragging = false
private var dragStartValue: Float = 0 //
private var dragStartTickIndex: Int = 0 //
private var currentSnappedTickIndex: Int = 0 //
private var lastGestureValue: Float = 0 //
// 5
private var tickValues: [Int] {
let step = (maxFontSize - minFontSize) / 4 // 54
return (0...4).map { minFontSize + $0 * step }
}
func configure(with row: RowModel) {
guard let row = row as? FontRow else { return }
@ -129,6 +152,8 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
if let size = Int(row.count) {
currentFontSize = max(minFontSize, min(maxFontSize, size))
//
currentFontSize = alignToNearestTick(currentFontSize)
updateFontSize(currentFontSize, updateSlider: true)
}
}
@ -136,7 +161,6 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
configureViews()
setupTickMarks()
}
required init?(coder: NSCoder) {
@ -149,14 +173,15 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
containerView.addSubview(fontSizeLab)
containerView.addSubview(sliderContainer)
// slider tick marks
// slider
sliderContainer.addSubview(minusButton)
sliderContainer.addSubview(plusButton)
sliderContainer.addSubview(customTrackView)
customTrackView.addSubview(trackLine)
sliderContainer.addSubview(slider)
sliderContainer.addSubview(tickMarksContainer)
// tick marks
sliderContainer.bringSubviewToFront(tickMarksContainer)
// 线线
// 线 setupTrackAndTickMarks() customTrackView
iconImgView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(12)
@ -198,102 +223,219 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
make.height.equalTo(30)
}
// tick marks slider 穿 slider
tickMarksContainer.snp.makeConstraints { make in
// slider track
customTrackView.snp.makeConstraints { make in
make.left.right.equalTo(slider)
make.centerY.equalTo(slider)
make.height.equalTo(slider).offset(4) // tick marks
make.height.equalTo(11) // 11pt 线
}
// tick marks
tickMarksContainer.isUserInteractionEnabled = false
// 线 5pt
trackLine.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.centerY.equalToSuperview()
make.height.equalTo(5)
}
}
private func setupTickMarks() {
// 5tick marks
private func setupTrackAndTickMarks() {
guard !isTickMarksCreated else { return }
// 5 线5*11线
let tickCount = 5
for _ in 0..<tickCount {
let tick = UIView()
tick.backgroundColor = UIColor(hex: "#CCCCCC")
tick.layer.cornerRadius = 2
tick.backgroundColor = UIColor(hex: "#A4A8B7")
tick.isUserInteractionEnabled = false //
tickMarksContainer.addSubview(tick)
//
tick.layer.cornerRadius = 2.5 // 52.5
tick.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner]
customTrackView.addSubview(tick)
tickMarks.append(tick)
}
isTickMarksCreated = true
}
override func layoutSubviews() {
super.layoutSubviews()
// tick markslayout
// 线 bounds
if customTrackView.bounds.width > 0 {
setupTrackAndTickMarks()
// 线 layout
updateTickMarksLayout()
// 线 isHidden false
tickMarks.forEach { $0.isHidden = false }
}
}
override func didMoveToSuperview() {
super.didMoveToSuperview()
// 线
if superview != nil {
DispatchQueue.main.async { [weak self] in
self?.ensureTickMarksVisible()
}
}
}
private func ensureTickMarksVisible() {
if customTrackView.bounds.width > 0 && !isTickMarksCreated {
setupTrackAndTickMarks()
}
updateTickMarksLayout()
// 线
tickMarks.forEach {
$0.isHidden = false
$0.backgroundColor = UIColor(hex: "#A4A8B7")
}
}
private func updateTickMarksLayout() {
guard tickMarks.count == 5, tickMarksContainer.bounds.width > 0 else { return }
let totalWidth = tickMarksContainer.bounds.width
// 54
guard customTrackView.bounds.width > 0 else { return }
let totalWidth = customTrackView.bounds.width
// 5 线线 4
let spacing = totalWidth / 4.0
let tickSize: CGFloat = 4
let centerY = tickMarksContainer.bounds.height / 2.0
let tickWidth: CGFloat = 5
let tickHeight: CGFloat = 11
let centerY = customTrackView.bounds.height / 2.0
for (index, tick) in tickMarks.enumerated() {
let centerX = CGFloat(index) * spacing
tick.frame = CGRect(
x: centerX - tickSize / 2.0,
y: centerY - tickSize / 2.0,
width: tickSize,
height: tickSize
x: centerX - tickWidth / 2.0,
y: centerY - tickHeight / 2.0,
width: tickWidth,
height: tickHeight
)
tick.layer.cornerRadius = tickSize / 2.0
tick.isHidden = false //
}
}
// slider
private func tickIndexForValue(_ value: Float) -> Int {
let normalizedValue = (value - Float(minFontSize)) / Float(maxFontSize - minFontSize)
let index = Int(round(normalizedValue * 4)) // 0-4
return max(0, min(4, index))
}
// slider
private func valueForTickIndex(_ index: Int) -> Float {
guard index >= 0 && index < tickValues.count else { return Float(currentFontSize) }
return Float(tickValues[index])
}
//
private func alignToNearestTick(_ value: Int) -> Int {
return tickValues.min(by: { abs($0 - value) < abs($1 - value) }) ?? value
}
//
private func currentTickIndex() -> Int {
if let index = tickValues.firstIndex(of: currentFontSize) {
return index
}
//
let nearest = alignToNearestTick(currentFontSize)
return tickValues.firstIndex(of: nearest) ?? 2 //
}
@objc private func decreaseFontSize() {
if currentFontSize > minFontSize {
currentFontSize -= 1
let currentIndex = currentTickIndex()
if currentIndex > 0 {
currentFontSize = tickValues[currentIndex - 1]
updateFontSize(currentFontSize, updateSlider: true)
}
}
@objc private func increaseFontSize() {
if currentFontSize < maxFontSize {
currentFontSize += 1
let currentIndex = currentTickIndex()
if currentIndex < tickValues.count - 1 {
currentFontSize = tickValues[currentIndex + 1]
updateFontSize(currentFontSize, updateSlider: true)
}
}
@objc private func sliderValueChanged(_ slider: UISlider) {
//
let newValue = Int(round(slider.value))
if newValue != currentFontSize {
currentFontSize = newValue
updateFontSize(currentFontSize, updateSlider: false) // slider
guard isDragging else { return }
//
//
let currentGestureValue = slider.value
let snappedValue = valueForTickIndex(currentSnappedTickIndex)
let valueDelta = currentGestureValue - snappedValue
let range = Float(maxFontSize - minFontSize)
let tickDelta = valueDelta / (range / 4.0) // 0-4
// 线 = 0.5
let threshold: Float = 0.5
//
var targetTickIndex = currentSnappedTickIndex
if tickDelta > threshold && currentSnappedTickIndex < 4 {
//
targetTickIndex = currentSnappedTickIndex + 1
} else if tickDelta < -threshold && currentSnappedTickIndex > 0 {
//
targetTickIndex = currentSnappedTickIndex - 1
}
//
if targetTickIndex != currentSnappedTickIndex {
let targetValue = valueForTickIndex(targetTickIndex)
// 使 UIView.performWithoutAnimation
UIView.performWithoutAnimation {
slider.setValue(targetValue, animated: false) //
}
currentFontSize = tickValues[targetTickIndex]
fontSizeLab.text = "\(currentFontSize)"
//
currentSnappedTickIndex = targetTickIndex
lastGestureValue = targetValue
} else {
// slider
//
if abs(currentGestureValue - snappedValue) > 0.01 {
//
UIView.performWithoutAnimation {
slider.setValue(snappedValue, animated: false)
}
}
//
fontSizeLab.text = "\(tickValues[currentSnappedTickIndex])"
lastGestureValue = currentGestureValue
}
}
@objc private func sliderTouchDown(_ slider: UISlider) {
//
//
isDragging = true
dragStartValue = slider.value
dragStartTickIndex = tickIndexForValue(slider.value)
currentSnappedTickIndex = dragStartTickIndex //
lastGestureValue = slider.value
}
@objc private func sliderTouchUp(_ slider: UISlider) {
//
let newValue = Int(round(slider.value))
currentFontSize = newValue
updateFontSize(currentFontSize, updateSlider: true)
//
isDragging = false
let finalTickIndex = tickIndexForValue(slider.value)
currentFontSize = tickValues[finalTickIndex]
slider.setValue(Float(currentFontSize), animated: true) //
updateFontSize(currentFontSize, updateSlider: false)
}
private func updateFontSize(_ size: Int, updateSlider: Bool = true) {
fontSizeLab.text = "\(size)"
//
let alignedSize = alignToNearestTick(size)
fontSizeLab.text = "\(alignedSize)"
if updateSlider {
slider.setValue(Float(size), animated: false)
// 使 slider使
slider.setValue(Float(alignedSize), animated: true)
}
//
// minusButton.isEnabled = size > minFontSize
// plusButton.isEnabled = size < maxFontSize
//
// delegate?.fontSizeChanged(to: size)
// delegate?.fontSizeChanged(to: alignedSize)
}
}