// // LNFiveStarScoreView.swift // Lanu // // Created by OneeChan on 2025/11/14. // import Foundation import UIKit import SnapKit protocol LNFiveStarScoreViewDelegate: AnyObject { func onFiveStarScoreView(view: LNFiveStarScoreView, scoreChanged newScore: Double) } class LNFiveStarScoreView: UIView { private let stackView = UIStackView() private var starViews: [LNStarScoreView] = [] weak var delegate: LNFiveStarScoreViewDelegate? = nil var score: Double = 0.0 { didSet { guard oldValue != score else { return } let fixed = score.bounded(min: 0.0, max: 5.0) for (index, view) in starViews.enumerated() { if fixed - 1 > Double(index) { view.score = 1.0 } else { view.score = fixed - Double(index) } } delegate?.onFiveStarScoreView(view: self, scoreChanged: score) } } var icSize: CGFloat? { didSet { starViews.forEach { $0.icSize = icSize } } } var spacing: CGFloat? { didSet { stackView.spacing = spacing ?? 0 } } var startType: LNStarType = .normal { didSet { starViews.forEach { $0.curType = startType } } } var editable: Bool = false var unit: Double = 1 override init(frame: CGRect) { super.init(frame: frame) setupViews() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } } extension LNFiveStarScoreView { @objc private func handleTap(_ tap: UITapGestureRecognizer) { guard editable else { return } let position = tap.location(in: self) for (index, view) in starViews.enumerated() { if CGRectContainsPoint(view.frame, position) { let offset = position.x - view.frame.origin.x var newScore = offset / view.frame.width newScore = ceil(newScore / unit) * unit score = newScore + Double(index) break } } } } extension LNFiveStarScoreView { private func setupViews() { stackView.axis = .horizontal stackView.distribution = .equalSpacing addSubview(stackView) stackView.snp.makeConstraints { make in make.edges.equalToSuperview() } for _ in 0..<5 { let star = LNStarScoreView() stackView.addArrangedSubview(star) starViews.append(star) } let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap)) addGestureRecognizer(tap) } } #if DEBUG import SwiftUI struct LNFiveStarScoreViewPreview: UIViewRepresentable { func makeUIView(context: Context) -> some UIView { let container = UIView() container.backgroundColor = .lightGray let view = LNFiveStarScoreView() view.startType = .whiteBorder view.editable = true container.addSubview(view) view.snp.makeConstraints { make in make.leading.centerY.equalToSuperview() make.width.equalTo(300) } view.score = 1.3 return container } func updateUIView(_ uiView: UIViewType, context: Context) { } } #Preview(body: { LNFiveStarScoreViewPreview() }) #endif