Swiftgram/submodules/GraphUI/Sources/ChartVisibilityView.swift
2020-03-23 15:26:40 +04:00

158 lines
6.0 KiB
Swift

//
// ChartVisibilityView.swift
// GraphTest
//
// Created by Andrei Salavei on 4/13/19.
// Copyright © 2019 Andrei Salavei. All rights reserved.
//
import UIKit
import GraphCore
import Display
private enum Constants {
static let itemHeight: CGFloat = 30
static let itemSpacing: CGFloat = 8
static let labelTextApproxInsets: CGFloat = 40
static let insets = UIEdgeInsets(top: 0, left: 16, bottom: 16, right: 16)
}
public func calculateVisiblityHeight(width: CGFloat, items: [ChartVisibilityItem]) -> CGFloat {
let frames = generateItemsFrames(frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude)), items: items)
guard let lastFrame = frames.last else { return .zero }
return lastFrame.maxY + Constants.insets.bottom
}
private func generateItemsFrames(frame: CGRect, items: [ChartVisibilityItem]) -> [CGRect] {
var previousPoint = CGPoint(x: Constants.insets.left, y: Constants.insets.top)
var frames: [CGRect] = []
for item in items {
let labelSize = (item.title as NSString).size(withAttributes: [.font: ChartVisibilityItemView.textFont])
let width = (labelSize.width + Constants.labelTextApproxInsets).rounded(.up)
if previousPoint.x + width < (frame.width - Constants.insets.left - Constants.insets.right) {
frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: Constants.itemHeight)))
} else if previousPoint.x <= Constants.insets.left {
frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: Constants.itemHeight)))
} else {
previousPoint.y += Constants.itemHeight + Constants.itemSpacing
previousPoint.x = Constants.insets.left
frames.append(CGRect(origin: previousPoint, size: CGSize(width: width, height: Constants.itemHeight)))
}
previousPoint.x += width + Constants.itemSpacing
}
return frames
}
class ChartVisibilityView: UIView {
var items: [ChartVisibilityItem] = [] {
didSet {
selectedItems = items.map { _ in true }
while selectionViews.count > selectedItems.count {
selectionViews.last?.removeFromSuperview()
selectionViews.removeLast()
}
while selectionViews.count < selectedItems.count {
let view = ChartVisibilityItemView(frame: bounds)
addSubview(view)
selectionViews.append(view)
}
for (index, item) in items.enumerated() {
let view = selectionViews[index]
view.item = item
view.tapClosure = { [weak self, weak view] in
guard let self = self else { return }
let selected = !self.selectedItems[index]
let selectedItemsCount = self.selectedItems.filter { $0 }.count
if selectedItemsCount == 1 && !selected {
view?.layer.addShakeAnimation()
} else {
self.setItemSelected(selected, at: index, animated: true)
self.notifyItemSelection()
}
}
view.longTapClosure = { [weak self] in
guard let self = self else { return }
let hasSelectedItem = self.selectedItems.enumerated().contains(where: { $0.element && $0.offset != index })
if hasSelectedItem {
for (itemIndex, _) in self.items.enumerated() {
self.setItemSelected(itemIndex == index, at: itemIndex, animated: true)
}
} else {
for (itemIndex, _) in self.items.enumerated() {
self.setItemSelected(true, at: itemIndex, animated: true)
}
}
self.notifyItemSelection()
}
}
}
}
private (set) var selectedItems: [Bool] = []
var isExpanded: Bool = true {
didSet {
invalidateIntrinsicContentSize()
setNeedsUpdateConstraints()
}
}
private var selectionViews: [ChartVisibilityItemView] = []
var selectionCallbackClosure: (([Bool]) -> Void)?
func setItemSelected(_ selected: Bool, at index: Int, animated: Bool) {
self.selectedItems[index] = selected
self.selectionViews[index].setChecked(isChecked: selected, animated: animated)
}
func setItemsSelection(_ selection: [Bool]) {
assert(selection.count == items.count)
self.selectedItems = selection
for (index, selected) in self.selectedItems.enumerated() {
selectionViews[index].setChecked(isChecked: selected, animated: false)
}
}
private func notifyItemSelection() {
selectionCallbackClosure?(selectedItems)
}
override func layoutSubviews() {
super.layoutSubviews()
updateFrames()
}
private func updateFrames() {
for (index, frame) in generateItemsFrames(frame: bounds, items: self.items).enumerated() {
selectionViews[index].frame = frame
}
}
override var intrinsicContentSize: CGSize {
guard isExpanded else {
var size = self.bounds.size
size.height = 0
return size
}
let frames = generateItemsFrames(frame: UIScreen.main.bounds, items: self.items)
guard let lastFrame = frames.last else { return .zero }
let size = CGSize(width: frame.width, height: lastFrame.maxY + Constants.insets.bottom)
return size
}
}
extension ChartVisibilityView: ChartThemeContainer {
func apply(theme: ChartTheme, animated: Bool) {
UIView.perform(animated: animated) {
self.backgroundColor = theme.chartBackgroundColor
}
}
}