session-ios/SignalUtilitiesKit/Shared Views/GalleryRailView.swift

267 lines
8.1 KiB
Swift
Raw Normal View History

2018-11-14 00:02:48 +01:00
//
// Copyright (c) 2019 Open Whisper Systems. All rights reserved.
2018-11-14 00:02:48 +01:00
//
import PromiseKit
2020-11-09 06:03:59 +01:00
import SessionUIKit
2018-11-14 00:02:48 +01:00
2018-11-15 03:01:21 +01:00
public protocol GalleryRailItemProvider: class {
2018-11-14 00:02:48 +01:00
var railItems: [GalleryRailItem] { get }
}
2018-11-15 03:01:21 +01:00
public protocol GalleryRailItem: class {
2019-04-20 00:21:00 +02:00
func buildRailItemView() -> UIView
2018-11-14 00:02:48 +01:00
}
protocol GalleryRailCellViewDelegate: class {
func didTapGalleryRailCellView(_ galleryRailCellView: GalleryRailCellView)
}
2018-11-15 03:01:21 +01:00
public class GalleryRailCellView: UIView {
2018-11-14 00:02:48 +01:00
weak var delegate: GalleryRailCellViewDelegate?
override init(frame: CGRect) {
super.init(frame: frame)
layoutMargins = .zero
clipsToBounds = false
2019-04-20 00:21:00 +02:00
addSubview(contentContainer)
contentContainer.autoPinEdgesToSuperviewMargins()
2019-04-20 00:41:36 +02:00
contentContainer.layer.cornerRadius = 4.8
2018-11-14 00:02:48 +01:00
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(didTap(sender:)))
addGestureRecognizer(tapGesture)
}
public required init?(coder aDecoder: NSCoder) {
2018-11-14 00:02:48 +01:00
fatalError("init(coder:) has not been implemented")
}
// MARK: Actions
@objc
func didTap(sender: UITapGestureRecognizer) {
self.delegate?.didTapGalleryRailCellView(self)
}
// MARK:
var item: GalleryRailItem?
func configure(item: GalleryRailItem, delegate: GalleryRailCellViewDelegate) {
self.item = item
self.delegate = delegate
2019-04-20 00:21:00 +02:00
for view in contentContainer.subviews {
view.removeFromSuperview()
}
2018-11-14 00:02:48 +01:00
2019-04-20 00:21:00 +02:00
let itemView = item.buildRailItemView()
contentContainer.addSubview(itemView)
itemView.autoPinEdgesToSuperviewEdges()
2018-11-14 00:02:48 +01:00
}
// MARK: Selected
private(set) var isSelected: Bool = false
2019-04-20 00:41:36 +02:00
public let cellBorderWidth: CGFloat = 3
2019-03-12 18:27:35 +01:00
2018-11-14 00:02:48 +01:00
func setIsSelected(_ isSelected: Bool) {
self.isSelected = isSelected
// Reserve space for the selection border whether or not the cell is selected.
2019-03-12 18:27:35 +01:00
layoutMargins = UIEdgeInsets(top: 0, left: cellBorderWidth, bottom: 0, right: cellBorderWidth)
2018-11-14 00:02:48 +01:00
if isSelected {
2020-03-17 06:18:53 +01:00
contentContainer.layer.borderColor = Colors.accent.cgColor
2019-04-20 00:21:00 +02:00
contentContainer.layer.borderWidth = cellBorderWidth
2018-11-14 00:02:48 +01:00
} else {
2019-04-20 00:21:00 +02:00
contentContainer.layer.borderWidth = 0
2018-11-14 00:02:48 +01:00
}
}
// MARK: Subview Helpers
2019-04-20 00:21:00 +02:00
let contentContainer: UIView = {
let view = UIView()
view.autoPinToSquareAspectRatio()
view.clipsToBounds = true
2018-11-14 00:02:48 +01:00
2019-04-20 00:21:00 +02:00
return view
2018-11-14 00:02:48 +01:00
}()
}
2018-11-15 03:01:21 +01:00
public protocol GalleryRailViewDelegate: class {
2018-11-14 00:02:48 +01:00
func galleryRailView(_ galleryRailView: GalleryRailView, didTapItem imageRailItem: GalleryRailItem)
}
2018-11-15 03:01:21 +01:00
public class GalleryRailView: UIView, GalleryRailCellViewDelegate {
2018-11-14 00:02:48 +01:00
2018-11-15 03:01:21 +01:00
public weak var delegate: GalleryRailViewDelegate?
public var cellViews: [GalleryRailCellView] = []
var cellViewItems: [GalleryRailItem] {
get { return cellViews.compactMap { $0.item } }
}
2018-11-14 00:02:48 +01:00
// MARK: Initializers
override init(frame: CGRect) {
super.init(frame: frame)
clipsToBounds = false
2018-11-14 00:02:48 +01:00
addSubview(scrollView)
scrollView.clipsToBounds = false
2018-11-14 00:02:48 +01:00
scrollView.layoutMargins = .zero
scrollView.autoPinEdgesToSuperviewMargins()
}
public required init?(coder aDecoder: NSCoder) {
2018-11-14 00:02:48 +01:00
fatalError("init(coder:) has not been implemented")
}
// MARK: Public
2019-04-20 00:21:00 +02:00
public func configureCellViews(itemProvider: GalleryRailItemProvider?, focusedItem: GalleryRailItem?, cellViewBuilder: (GalleryRailItem) -> GalleryRailCellView) {
2018-11-14 00:02:48 +01:00
let animationDuration: TimeInterval = 0.2
guard let itemProvider = itemProvider else {
UIView.animate(withDuration: animationDuration) {
self.isHidden = true
}
2018-11-15 03:01:21 +01:00
self.cellViews = []
2018-11-14 00:02:48 +01:00
return
}
let areRailItemsIdentical = { (lhs: [GalleryRailItem], rhs: [GalleryRailItem]) -> Bool in
guard lhs.count == rhs.count else {
return false
}
for (index, element) in lhs.enumerated() {
guard element === rhs[index] else {
return false
}
}
return true
}
if itemProvider === self.itemProvider, areRailItemsIdentical(itemProvider.railItems, self.cellViewItems) {
UIView.animate(withDuration: animationDuration) {
self.updateFocusedItem(focusedItem)
self.layoutIfNeeded()
}
}
self.itemProvider = itemProvider
guard itemProvider.railItems.count > 1 else {
2018-11-14 17:09:24 +01:00
let cellViews = scrollView.subviews
UIView.animate(withDuration: animationDuration,
animations: {
cellViews.forEach { $0.isHidden = true }
self.isHidden = true
},
completion: { _ in cellViews.forEach { $0.removeFromSuperview() } })
2018-11-15 03:01:21 +01:00
self.cellViews = []
2018-11-14 00:02:48 +01:00
return
}
2018-11-14 17:09:24 +01:00
scrollView.subviews.forEach { $0.removeFromSuperview() }
2018-11-14 00:02:48 +01:00
UIView.animate(withDuration: animationDuration) {
self.isHidden = false
}
let cellViews = buildCellViews(items: itemProvider.railItems, cellViewBuilder: cellViewBuilder)
2018-11-14 00:02:48 +01:00
self.cellViews = cellViews
let stackView = UIStackView(arrangedSubviews: cellViews)
stackView.axis = .horizontal
2019-04-20 00:41:36 +02:00
stackView.spacing = 0
stackView.clipsToBounds = false
2018-11-14 00:02:48 +01:00
scrollView.addSubview(stackView)
stackView.autoPinEdgesToSuperviewEdges()
stackView.autoMatch(.height, to: .height, of: scrollView)
updateFocusedItem(focusedItem)
}
// MARK: GalleryRailCellViewDelegate
func didTapGalleryRailCellView(_ galleryRailCellView: GalleryRailCellView) {
guard let item = galleryRailCellView.item else {
owsFailDebug("item was unexpectedly nil")
return
}
delegate?.galleryRailView(self, didTapItem: item)
}
// MARK: Subview Helpers
private var itemProvider: GalleryRailItemProvider?
private let scrollView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.isScrollEnabled = true
return scrollView
}()
2019-04-20 00:21:00 +02:00
private func buildCellViews(items: [GalleryRailItem], cellViewBuilder: (GalleryRailItem) -> GalleryRailCellView) -> [GalleryRailCellView] {
2018-11-14 00:02:48 +01:00
return items.map { item in
2019-04-20 00:21:00 +02:00
let cellView = cellViewBuilder(item)
2018-11-14 00:02:48 +01:00
cellView.configure(item: item, delegate: self)
return cellView
}
}
2018-11-15 03:01:21 +01:00
enum ScrollFocusMode {
case keepCentered, keepWithinBounds
2018-11-14 00:02:48 +01:00
}
2018-11-15 03:01:21 +01:00
var scrollFocusMode: ScrollFocusMode = .keepCentered
2018-11-14 00:02:48 +01:00
func updateFocusedItem(_ focusedItem: GalleryRailItem?) {
var selectedCellView: GalleryRailCellView?
cellViews.forEach { cellView in
if cellView.item === focusedItem {
assert(selectedCellView == nil)
selectedCellView = cellView
cellView.setIsSelected(true)
} else {
cellView.setIsSelected(false)
}
}
self.layoutIfNeeded()
2018-11-15 03:01:21 +01:00
switch scrollFocusMode {
case .keepCentered:
guard let selectedCell = selectedCellView else {
owsFailDebug("selectedCell was unexpectedly nil")
return
}
let cellViewCenter = selectedCell.superview!.convert(selectedCell.center, to: scrollView)
let additionalInset = scrollView.center.x - cellViewCenter.x
2018-11-14 00:02:48 +01:00
2018-11-15 03:01:21 +01:00
var inset = scrollView.contentInset
inset.left = additionalInset
scrollView.contentInset = inset
var offset = scrollView.contentOffset
offset.x = -additionalInset
scrollView.contentOffset = offset
case .keepWithinBounds:
guard let selectedCell = selectedCellView else {
owsFailDebug("selectedCell was unexpectedly nil")
return
}
2018-11-14 00:02:48 +01:00
2018-11-15 03:01:21 +01:00
let cellFrame = selectedCell.superview!.convert(selectedCell.frame, to: scrollView)
2018-11-14 00:02:48 +01:00
2018-11-15 03:01:21 +01:00
scrollView.scrollRectToVisible(cellFrame, animated: true)
}
}
}