// // MediaGridContainerView.swift // MediaGridContainerView // // Created by Cirno MainasuK on 2021-8-23. // Copyright © 2021 Twidere. All rights reserved. // import os.log import UIKit import func AVFoundation.AVMakeRect public protocol MediaGridContainerViewDelegate: AnyObject { func mediaGridContainerView(_ container: MediaGridContainerView, didTapMediaView mediaView: MediaView, at index: Int) func mediaGridContainerView(_ container: MediaGridContainerView, mediaSensitiveButtonDidPressed button: UIButton) } public final class MediaGridContainerView: UIView { static let sensitiveToggleButtonSize = CGSize(width: 34, height: 34) public static let maxCount = 9 let logger = Logger(subsystem: "MediaGridContainerView", category: "UI") public weak var delegate: MediaGridContainerViewDelegate? public private(set) lazy var viewModel: ViewModel = { let viewModel = ViewModel() viewModel.bind(view: self) return viewModel }() // lazy var is required here to setup gesture recognizer target-action // Swift not doesn't emit compiler error if without `lazy` here private(set) lazy var _mediaViews: [MediaView] = { var mediaViews: [MediaView] = [] for i in 0.. MediaView { prepareForReuse() let mediaView = _mediaViews[0] layout.layout(in: self, mediaView: mediaView) layoutContentWarningOverlay() bringSubviewToFront(contentWarningOverlay) return mediaView } public func dequeueMediaView(gridLayout layout: GridLayout) -> [MediaView] { prepareForReuse() let mediaViews = Array(_mediaViews[0.. UIStackView { let stackView = UIStackView() stackView.axis = axis stackView.semanticContentAttribute = .forceLeftToRight stackView.spacing = GridLayout.spacing stackView.distribution = .fillEqually return stackView } public func layout(in view: UIView, mediaViews: [MediaView]) { let containerVerticalStackView = createStackView(axis: .vertical) containerVerticalStackView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(containerVerticalStackView) containerVerticalStackView.pinToParent() let count = mediaViews.count switch count { case 1: assertionFailure("should use Adaptive Layout") containerVerticalStackView.addArrangedSubview(mediaViews[0]) case 2: let horizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(horizontalStackView) horizontalStackView.addArrangedSubview(mediaViews[0]) horizontalStackView.addArrangedSubview(mediaViews[1]) case 3: let horizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(horizontalStackView) horizontalStackView.addArrangedSubview(mediaViews[0]) let verticalStackView = createStackView(axis: .vertical) horizontalStackView.addArrangedSubview(verticalStackView) verticalStackView.addArrangedSubview(mediaViews[1]) verticalStackView.addArrangedSubview(mediaViews[2]) case 4: let topHorizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(topHorizontalStackView) topHorizontalStackView.addArrangedSubview(mediaViews[0]) topHorizontalStackView.addArrangedSubview(mediaViews[1]) let bottomHorizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(bottomHorizontalStackView) bottomHorizontalStackView.addArrangedSubview(mediaViews[2]) bottomHorizontalStackView.addArrangedSubview(mediaViews[3]) case 5...9: let topHorizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(topHorizontalStackView) topHorizontalStackView.addArrangedSubview(mediaViews[0]) topHorizontalStackView.addArrangedSubview(mediaViews[1]) topHorizontalStackView.addArrangedSubview(mediaViews[2]) func mediaViewOrPlaceholderView(at index: Int) -> UIView { return index < mediaViews.count ? mediaViews[index] : UIView() } let middleHorizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(middleHorizontalStackView) middleHorizontalStackView.addArrangedSubview(mediaViews[3]) middleHorizontalStackView.addArrangedSubview(mediaViews[4]) middleHorizontalStackView.addArrangedSubview(mediaViewOrPlaceholderView(at: 5)) if count > 6 { let bottomHorizontalStackView = createStackView(axis: .horizontal) containerVerticalStackView.addArrangedSubview(bottomHorizontalStackView) bottomHorizontalStackView.addArrangedSubview(mediaViewOrPlaceholderView(at: 6)) bottomHorizontalStackView.addArrangedSubview(mediaViewOrPlaceholderView(at: 7)) bottomHorizontalStackView.addArrangedSubview(mediaViewOrPlaceholderView(at: 8)) } default: assertionFailure() return } let containerWidth = maxSize.width let containerHeight = count > 6 ? containerWidth : containerWidth * 2 / 3 NSLayoutConstraint.activate([ view.widthAnchor.constraint(equalToConstant: containerWidth).priority(.required - 1), view.heightAnchor.constraint(equalToConstant: containerHeight).priority(.required - 1), ]) } } }