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.maximumValue = Float(maxFontSize)
slider.value = Float(currentFontSize) slider.value = Float(currentFontSize)
// track // track
slider.minimumTrackTintColor = UIColor(hex: "#E0E0E0") slider.minimumTrackTintColor = .clear
slider.maximumTrackTintColor = UIColor(hex: "#E0E0E0") slider.maximumTrackTintColor = .clear
// thumb // thumb
let thumbSize: CGFloat = 24 let thumbSize: CGFloat = 20
UIGraphicsBeginImageContextWithOptions(CGSize(width: thumbSize, height: thumbSize), false, 0.0) UIGraphicsBeginImageContextWithOptions(CGSize(width: thumbSize, height: thumbSize), false, 0.0)
if let context = UIGraphicsGetCurrentContext() { if let context = UIGraphicsGetCurrentContext() {
context.setFillColor(UIColor.white.cgColor) context.setFillColor(UIColor.white.cgColor)
context.fillEllipse(in: CGRect(x: 0, y: 0, width: thumbSize, height: thumbSize)) 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() let thumbImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext() UIGraphicsEndImageContext()
@ -103,7 +104,7 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
slider.setThumbImage(thumbImage, for: .normal) slider.setThumbImage(thumbImage, for: .normal)
slider.setThumbImage(thumbImage, for: .highlighted) slider.setThumbImage(thumbImage, for: .highlighted)
// //
slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged) slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
slider.addTarget(self, action: #selector(sliderTouchDown(_:)), for: .touchDown) slider.addTarget(self, action: #selector(sliderTouchDown(_:)), for: .touchDown)
slider.addTarget(self, action: #selector(sliderTouchUp(_:)), for: [.touchUpInside, .touchUpOutside]) slider.addTarget(self, action: #selector(sliderTouchUp(_:)), for: [.touchUpInside, .touchUpOutside])
@ -111,13 +112,35 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
return slider return slider
}() }()
private lazy var tickMarksContainer: UIView = { // 线线
private lazy var customTrackView: UIView = {
let view = UIView() let view = UIView()
view.backgroundColor = .clear view.backgroundColor = .clear
view.isUserInteractionEnabled = false //
return view 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 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) { func configure(with row: RowModel) {
guard let row = row as? FontRow else { return } guard let row = row as? FontRow else { return }
@ -129,6 +152,8 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
if let size = Int(row.count) { if let size = Int(row.count) {
currentFontSize = max(minFontSize, min(maxFontSize, size)) currentFontSize = max(minFontSize, min(maxFontSize, size))
//
currentFontSize = alignToNearestTick(currentFontSize)
updateFontSize(currentFontSize, updateSlider: true) updateFontSize(currentFontSize, updateSlider: true)
} }
} }
@ -136,7 +161,6 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier) super.init(style: style, reuseIdentifier: reuseIdentifier)
configureViews() configureViews()
setupTickMarks()
} }
required init?(coder: NSCoder) { required init?(coder: NSCoder) {
@ -149,14 +173,15 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
containerView.addSubview(fontSizeLab) containerView.addSubview(fontSizeLab)
containerView.addSubview(sliderContainer) containerView.addSubview(sliderContainer)
// slider tick marks // slider
sliderContainer.addSubview(minusButton) sliderContainer.addSubview(minusButton)
sliderContainer.addSubview(plusButton) sliderContainer.addSubview(plusButton)
sliderContainer.addSubview(customTrackView)
customTrackView.addSubview(trackLine)
sliderContainer.addSubview(slider) sliderContainer.addSubview(slider)
sliderContainer.addSubview(tickMarksContainer)
// tick marks // 线线
sliderContainer.bringSubviewToFront(tickMarksContainer) // 线 setupTrackAndTickMarks() customTrackView
iconImgView.snp.makeConstraints { make in iconImgView.snp.makeConstraints { make in
make.top.equalToSuperview().offset(12) make.top.equalToSuperview().offset(12)
@ -198,102 +223,219 @@ class ChatFontCell: ChatSettingBaseCell, CellConfigurable {
make.height.equalTo(30) make.height.equalTo(30)
} }
// tick marks slider 穿 slider // slider track
tickMarksContainer.snp.makeConstraints { make in customTrackView.snp.makeConstraints { make in
make.left.right.equalTo(slider) make.left.right.equalTo(slider)
make.centerY.equalTo(slider) make.centerY.equalTo(slider)
make.height.equalTo(slider).offset(4) // tick marks make.height.equalTo(11) // 11pt 线
} }
// tick marks // 线 5pt
tickMarksContainer.isUserInteractionEnabled = false trackLine.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.centerY.equalToSuperview()
make.height.equalTo(5)
}
} }
private func setupTickMarks() { private func setupTrackAndTickMarks() {
// 5tick marks guard !isTickMarksCreated else { return }
// 5 线5*11线
let tickCount = 5 let tickCount = 5
for _ in 0..<tickCount { for _ in 0..<tickCount {
let tick = UIView() let tick = UIView()
tick.backgroundColor = UIColor(hex: "#CCCCCC") tick.backgroundColor = UIColor(hex: "#A4A8B7")
tick.layer.cornerRadius = 2
tick.isUserInteractionEnabled = false // 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) tickMarks.append(tick)
} }
isTickMarksCreated = true
} }
override func layoutSubviews() { override func layoutSubviews() {
super.layoutSubviews() super.layoutSubviews()
// tick markslayout // 线 bounds
if customTrackView.bounds.width > 0 {
setupTrackAndTickMarks()
// 线 layout
updateTickMarksLayout() 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() { private func updateTickMarksLayout() {
guard tickMarks.count == 5, tickMarksContainer.bounds.width > 0 else { return } guard customTrackView.bounds.width > 0 else { return }
let totalWidth = tickMarksContainer.bounds.width let totalWidth = customTrackView.bounds.width
// 54 // 5 线线 4
let spacing = totalWidth / 4.0 let spacing = totalWidth / 4.0
let tickSize: CGFloat = 4 let tickWidth: CGFloat = 5
let centerY = tickMarksContainer.bounds.height / 2.0 let tickHeight: CGFloat = 11
let centerY = customTrackView.bounds.height / 2.0
for (index, tick) in tickMarks.enumerated() { for (index, tick) in tickMarks.enumerated() {
let centerX = CGFloat(index) * spacing let centerX = CGFloat(index) * spacing
tick.frame = CGRect( tick.frame = CGRect(
x: centerX - tickSize / 2.0, x: centerX - tickWidth / 2.0,
y: centerY - tickSize / 2.0, y: centerY - tickHeight / 2.0,
width: tickSize, width: tickWidth,
height: tickSize 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() { @objc private func decreaseFontSize() {
if currentFontSize > minFontSize { let currentIndex = currentTickIndex()
currentFontSize -= 1 if currentIndex > 0 {
currentFontSize = tickValues[currentIndex - 1]
updateFontSize(currentFontSize, updateSlider: true) updateFontSize(currentFontSize, updateSlider: true)
} }
} }
@objc private func increaseFontSize() { @objc private func increaseFontSize() {
if currentFontSize < maxFontSize { let currentIndex = currentTickIndex()
currentFontSize += 1 if currentIndex < tickValues.count - 1 {
currentFontSize = tickValues[currentIndex + 1]
updateFontSize(currentFontSize, updateSlider: true) updateFontSize(currentFontSize, updateSlider: true)
} }
} }
@objc private func sliderValueChanged(_ slider: UISlider) { @objc private func sliderValueChanged(_ slider: UISlider) {
// guard isDragging else { return }
let newValue = Int(round(slider.value))
if newValue != currentFontSize { //
currentFontSize = newValue //
updateFontSize(currentFontSize, updateSlider: false) // slider 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) { @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) { @objc private func sliderTouchUp(_ slider: UISlider) {
// //
let newValue = Int(round(slider.value)) isDragging = false
currentFontSize = newValue let finalTickIndex = tickIndexForValue(slider.value)
updateFontSize(currentFontSize, updateSlider: true) currentFontSize = tickValues[finalTickIndex]
slider.setValue(Float(currentFontSize), animated: true) //
updateFontSize(currentFontSize, updateSlider: false)
} }
private func updateFontSize(_ size: Int, updateSlider: Bool = true) { private func updateFontSize(_ size: Int, updateSlider: Bool = true) {
fontSizeLab.text = "\(size)" //
let alignedSize = alignToNearestTick(size)
fontSizeLab.text = "\(alignedSize)"
if updateSlider { 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)
} }
} }