LNFiveStarScoreView.swift 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. //
  2. // LNFiveStarScoreView.swift
  3. // Lanu
  4. //
  5. // Created by OneeChan on 2025/11/14.
  6. //
  7. import Foundation
  8. import UIKit
  9. import SnapKit
  10. protocol LNFiveStarScoreViewDelegate: NSObject {
  11. func onFiveStarScoreView(view: LNFiveStarScoreView, scoreChanged newScore: Double)
  12. }
  13. class LNFiveStarScoreView: UIView {
  14. private let stackView = UIStackView()
  15. private var starViews: [LNStarScoreView] = []
  16. weak var delegate: LNFiveStarScoreViewDelegate? = nil
  17. var score: Double = 0.0 {
  18. didSet {
  19. guard oldValue != score else { return }
  20. let fixed = score.bounded(min: 0.0, max: 5.0)
  21. for (index, view) in starViews.enumerated() {
  22. if fixed - 1 > Double(index) {
  23. view.score = 1.0
  24. } else {
  25. view.score = fixed - Double(index)
  26. }
  27. }
  28. delegate?.onFiveStarScoreView(view: self, scoreChanged: score)
  29. }
  30. }
  31. var icSize: CGFloat? {
  32. didSet { starViews.forEach { $0.icSize = icSize } }
  33. }
  34. var spacing: CGFloat? {
  35. didSet { stackView.spacing = spacing ?? 0 }
  36. }
  37. var startType: LNStarType = .normal {
  38. didSet { starViews.forEach { $0.curType = startType } }
  39. }
  40. var editable: Bool = false
  41. var unit: Double = 1
  42. override init(frame: CGRect) {
  43. super.init(frame: frame)
  44. setupViews()
  45. }
  46. required init?(coder: NSCoder) {
  47. fatalError("init(coder:) has not been implemented")
  48. }
  49. }
  50. extension LNFiveStarScoreView {
  51. @objc
  52. private func handleTap(_ tap: UITapGestureRecognizer) {
  53. guard editable else { return }
  54. let position = tap.location(in: self)
  55. for (index, view) in starViews.enumerated() {
  56. if CGRectContainsPoint(view.frame, position) {
  57. let offset = position.x - view.frame.origin.x
  58. var newScore = offset / view.frame.width
  59. newScore = ceil(newScore / unit) * unit
  60. score = newScore + Double(index)
  61. break
  62. }
  63. }
  64. }
  65. }
  66. extension LNFiveStarScoreView {
  67. private func setupViews() {
  68. stackView.axis = .horizontal
  69. stackView.distribution = .equalSpacing
  70. addSubview(stackView)
  71. stackView.snp.makeConstraints { make in
  72. make.edges.equalToSuperview()
  73. }
  74. for _ in 0..<5 {
  75. let star = LNStarScoreView()
  76. stackView.addArrangedSubview(star)
  77. starViews.append(star)
  78. }
  79. let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
  80. addGestureRecognizer(tap)
  81. }
  82. }
  83. #if DEBUG
  84. import SwiftUI
  85. struct LNFiveStarScoreViewPreview: UIViewRepresentable {
  86. func makeUIView(context: Context) -> some UIView {
  87. let container = UIView()
  88. container.backgroundColor = .lightGray
  89. let view = LNFiveStarScoreView()
  90. view.startType = .whiteBorder
  91. view.editable = true
  92. container.addSubview(view)
  93. view.snp.makeConstraints { make in
  94. make.leading.centerY.equalToSuperview()
  95. make.width.equalTo(300)
  96. }
  97. view.score = 1.3
  98. return container
  99. }
  100. func updateUIView(_ uiView: UIViewType, context: Context) { }
  101. }
  102. #Preview(body: {
  103. LNFiveStarScoreViewPreview()
  104. })
  105. #endif