parent
36d13b2769
commit
5604910471
@ -0,0 +1,639 @@
|
||||
//
|
||||
// SegmentControl.swift
|
||||
// IndieMusic
|
||||
//
|
||||
// Created by WenLei on 2023/11/15.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
struct SegmentControlSetting {
|
||||
|
||||
static let itemTextColor = UIColor.init(hex: 0xFFFFFF, alpha: 0.4)
|
||||
static let itemSelectedTextColor = UIColor.init(hex: 0xFFFFFF, alpha: 1)
|
||||
|
||||
static let itemBackgroundColor = color(red: 255.0, green: 250.0, blue: 250.0, alpha: 1.0)
|
||||
static let itemSelectedBackgroundColor = color(red: 255.0, green: 250.0, blue: 250.0, alpha: 1.0)
|
||||
|
||||
static let itemBorder : CGFloat = 30.0
|
||||
//MARK - Text font
|
||||
static let textFont = UIFont.systemFont(ofSize: 16.0)
|
||||
static let selectedTextFont = UIFont.systemFont(ofSize: 16.0)
|
||||
|
||||
//MARK - slider
|
||||
static let selectedViewBackgroundColor = UIColor.orange
|
||||
static let selectedViewpadding : CGFloat = 20.0
|
||||
|
||||
//MARK - bridge
|
||||
static let bridgeColor = UIColor.red
|
||||
static let bridgeWidth : CGFloat = 7.0
|
||||
|
||||
//MARK - divider
|
||||
static let dividerWidth : CGFloat = 2.0
|
||||
static let dividerpPadding : CGFloat = 10.0
|
||||
|
||||
|
||||
//MARK - inline func
|
||||
@inline(__always) static func color(red:Float, green:Float, blue:Float, alpha:Float) -> UIColor {
|
||||
return UIColor.init(red: CGFloat(red/255.0), green: CGFloat(green/255.0), blue: CGFloat(blue/255.0), alpha: CGFloat(alpha))
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate class SelectedBackgroundView : UIView {
|
||||
|
||||
lazy var cornerMask: CAShapeLayer = {
|
||||
let mask = CAShapeLayer()
|
||||
return mask
|
||||
}()
|
||||
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
}
|
||||
|
||||
override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
fileprivate var color : UIColor? {
|
||||
didSet{
|
||||
self.backgroundColor = color
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate enum SegmentItemViewState : Int {
|
||||
case Normal
|
||||
case Selected
|
||||
}
|
||||
|
||||
fileprivate class SegmentItemView : UIView {
|
||||
|
||||
fileprivate func itemWidth() -> CGFloat {
|
||||
|
||||
if let text = titleLabel.text {
|
||||
let string = text as NSString
|
||||
let size = string.size(withAttributes: [NSAttributedString.Key.font:selectedFont!])
|
||||
return size.width + SegmentControlSetting.itemBorder
|
||||
}
|
||||
|
||||
return 0.0
|
||||
}
|
||||
|
||||
fileprivate let titleLabel = UILabel()
|
||||
fileprivate lazy var bridgeView : CALayer = {
|
||||
let view = CALayer()
|
||||
let width = SegmentControlSetting.bridgeWidth
|
||||
view.bounds = CGRect(x: 0.0, y: 0.0, width: width, height: width)
|
||||
view.backgroundColor = SegmentControlSetting.bridgeColor.cgColor
|
||||
view.cornerRadius = view.bounds.size.width * 0.5
|
||||
return view
|
||||
}()
|
||||
|
||||
fileprivate lazy var dividerImageView : UIImageView = {
|
||||
let dividerImageView = UIImageView.init(image: UIImage.init(named: ""))
|
||||
dividerImageView.backgroundColor = .gray
|
||||
|
||||
return dividerImageView
|
||||
}()
|
||||
|
||||
|
||||
fileprivate var dividerWidth : CGFloat = 1 {
|
||||
didSet{
|
||||
if state == .Normal {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var dividerpPadding : CGFloat = 13 {
|
||||
didSet{
|
||||
if state == .Normal {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fileprivate func showBridge(show:Bool){
|
||||
self.bridgeView.isHidden = !show
|
||||
}
|
||||
|
||||
fileprivate var state : SegmentItemViewState = .Normal {
|
||||
didSet{
|
||||
updateItemView(state: state)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var font : UIFont?{
|
||||
didSet{
|
||||
if state == .Normal {
|
||||
self.titleLabel.font = font
|
||||
}
|
||||
}
|
||||
}
|
||||
fileprivate var selectedFont : UIFont?{
|
||||
didSet{
|
||||
if state == .Selected {
|
||||
self.titleLabel.font = selectedFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var text : String?{
|
||||
didSet{
|
||||
self.titleLabel.text = text
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var textColor : UIColor?{
|
||||
didSet{
|
||||
if state == .Normal {
|
||||
self.titleLabel.textColor = textColor
|
||||
}
|
||||
}
|
||||
}
|
||||
fileprivate var selectedTextColor : UIColor?{
|
||||
didSet{
|
||||
if state == .Selected {
|
||||
self.titleLabel.textColor = selectedTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var itemBackgroundColor : UIColor?{
|
||||
didSet{
|
||||
if state == .Normal {
|
||||
self.backgroundColor = .red
|
||||
}
|
||||
}
|
||||
}
|
||||
fileprivate var selectedBackgroundColor : UIColor?{
|
||||
didSet{
|
||||
if state == .Selected {
|
||||
self.backgroundColor = selectedBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate var textAlignment = NSTextAlignment.center {
|
||||
didSet{
|
||||
self.titleLabel.textAlignment = textAlignment
|
||||
}
|
||||
}
|
||||
|
||||
private func updateItemView(state: SegmentItemViewState){
|
||||
switch state {
|
||||
case .Normal:
|
||||
self.titleLabel.font = self.font
|
||||
self.titleLabel.textColor = self.textColor
|
||||
self.backgroundColor = self.itemBackgroundColor
|
||||
case .Selected:
|
||||
self.titleLabel.font = selectedFont
|
||||
self.titleLabel.textColor = self.selectedTextColor
|
||||
self.backgroundColor = self.selectedBackgroundColor
|
||||
}
|
||||
self.setNeedsLayout()
|
||||
self.layoutIfNeeded()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
|
||||
titleLabel.textAlignment = .center
|
||||
|
||||
addSubview(titleLabel)
|
||||
|
||||
bridgeView.isHidden = true
|
||||
layer.addSublayer(bridgeView)
|
||||
|
||||
layer.masksToBounds = true
|
||||
|
||||
addSubview(dividerImageView)
|
||||
}
|
||||
|
||||
fileprivate override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
backgroundColor = UIColor.clear
|
||||
|
||||
|
||||
titleLabel.sizeToFit()
|
||||
|
||||
titleLabel.center.x = bounds.size.width * 0.5
|
||||
titleLabel.center.y = bounds.size.height * 0.5
|
||||
|
||||
let width = bridgeView.bounds.size.width
|
||||
let x:CGFloat = titleLabel.frame.maxX
|
||||
bridgeView.frame = CGRect(x: x, y: bounds.midY - width, width: width, height: width)
|
||||
|
||||
dividerImageView.frame = CGRect(x: bounds.maxX - dividerWidth, y: bounds.minY + dividerpPadding, width: 1, height: bounds.size.height - dividerpPadding * 2)
|
||||
}
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@objc public protocol SegmentControlDelegate {
|
||||
@objc optional func didSelected(segement: SegmentControl, index: Int)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
open class SegmentControl: UIControl {
|
||||
|
||||
fileprivate struct Constants {
|
||||
static let height : CGFloat = 40.0
|
||||
}
|
||||
|
||||
open weak var delegate : SegmentControlDelegate?
|
||||
|
||||
open var autoAdjustWidth = false {
|
||||
didSet{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open func segementWidth() -> CGFloat {
|
||||
return bounds.size.width / (CGFloat)(itemViews.count)
|
||||
}
|
||||
|
||||
open func segmentWidth(index: Int) -> CGFloat {
|
||||
guard index >= 0 && index < itemViews.count else {
|
||||
return 0.0
|
||||
}
|
||||
if autoAdjustWidth {
|
||||
return itemViews[index].itemWidth()
|
||||
} else{
|
||||
return segementWidth()
|
||||
}
|
||||
}
|
||||
|
||||
open func selectedViewWidth(index: Int) -> CGFloat {
|
||||
guard index >= 0 && index < itemViews.count else {
|
||||
return segmentWidth(index: index)
|
||||
}
|
||||
if autoAdjustWidth {
|
||||
return itemViews[index].itemWidth() - SegmentControlSetting.selectedViewpadding
|
||||
} else{
|
||||
return segementWidth() - SegmentControlSetting.selectedViewpadding
|
||||
}
|
||||
}
|
||||
|
||||
open func selectedViewHeight() -> CGFloat {
|
||||
return 10
|
||||
}
|
||||
|
||||
open var selectedIndex = 0 {
|
||||
willSet{
|
||||
let originItem = self.itemViews[selectedIndex]
|
||||
originItem.state = .Normal
|
||||
|
||||
let selectItem = self.itemViews[newValue]
|
||||
selectItem.state = .Selected
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open var itemTextColor = SegmentControlSetting.itemTextColor{
|
||||
didSet{
|
||||
self.itemViews.forEach { (itemView) in
|
||||
itemView.textColor = itemTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var itemSelectedTextColor = SegmentControlSetting.itemSelectedTextColor{
|
||||
didSet{
|
||||
self.itemViews.forEach { (itemView) in
|
||||
itemView.selectedTextColor = itemSelectedTextColor
|
||||
}
|
||||
}
|
||||
}
|
||||
open var itemBackgroundColor = SegmentControlSetting.itemBackgroundColor{
|
||||
didSet{
|
||||
self.itemViews.forEach { (itemView) in
|
||||
itemView.itemBackgroundColor = itemBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var itemSelectedBackgroundColor = SegmentControlSetting.itemSelectedBackgroundColor{
|
||||
didSet{
|
||||
self.itemViews.forEach { (itemView) in
|
||||
itemView.selectedBackgroundColor = itemSelectedBackgroundColor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var sliderViewColor = SegmentControlSetting.selectedViewBackgroundColor{
|
||||
didSet{
|
||||
self.selectedBackgroundView.color = sliderViewColor
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
open var font = SegmentControlSetting.textFont{
|
||||
didSet{
|
||||
self.itemViews.forEach { (itemView) in
|
||||
itemView.font = font
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var selectedFont = SegmentControlSetting.selectedTextFont{
|
||||
didSet{
|
||||
self.itemViews.forEach { (itemView) in
|
||||
itemView.selectedFont = selectedFont
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
open var items : [String]? {
|
||||
didSet{
|
||||
guard items != nil && items!.count > 0 else {
|
||||
fatalError("Items cannot be empty")
|
||||
}
|
||||
|
||||
self.removeAllItemView()
|
||||
|
||||
|
||||
for title in items! {
|
||||
let view = self.createItemView(title: title)
|
||||
self.itemViews.append(view)
|
||||
self.contentView.addSubview(view)
|
||||
}
|
||||
self.selectedIndex = 0
|
||||
|
||||
self.contentView.sendSubviewToBack(self.selectedBackgroundView)
|
||||
}
|
||||
}
|
||||
|
||||
open func showBridge(show:Bool, index:Int){
|
||||
|
||||
guard index < itemViews.count && index >= 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
itemViews[index].showBridge(show: show)
|
||||
}
|
||||
|
||||
|
||||
|
||||
open var autoScrollWhenIndexChange = false
|
||||
|
||||
open var isShowSeparator = false
|
||||
|
||||
open var scrollToPointWhenIndexChanged : CGPoint?
|
||||
|
||||
open var bounces = false {
|
||||
didSet{
|
||||
self.scrollView.bounces = bounces
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func removeAllItemView() {
|
||||
itemViews.forEach { (label) in
|
||||
label.removeFromSuperview()
|
||||
}
|
||||
itemViews.removeAll()
|
||||
}
|
||||
private var itemWidths = [CGFloat]()
|
||||
private func createItemView(title:String) -> SegmentItemView {
|
||||
return createItemView(title: title,
|
||||
font: self.font,
|
||||
selectedFont: self.selectedFont,
|
||||
textColor: self.itemTextColor,
|
||||
selectedTextColor: self.itemSelectedTextColor,
|
||||
backgroundColor: UIColor.clear,
|
||||
selectedBackgroundColor: self.itemSelectedBackgroundColor
|
||||
)
|
||||
}
|
||||
|
||||
private func createItemView(title:String, font:UIFont, selectedFont:UIFont, textColor:UIColor, selectedTextColor:UIColor, backgroundColor:UIColor, selectedBackgroundColor:UIColor) -> SegmentItemView {
|
||||
let item = SegmentItemView()
|
||||
|
||||
item.text = title
|
||||
item.textColor = textColor
|
||||
item.textAlignment = .center
|
||||
item.font = font
|
||||
item.selectedFont = selectedFont
|
||||
|
||||
item.itemBackgroundColor = backgroundColor
|
||||
item.selectedTextColor = selectedTextColor
|
||||
item.selectedBackgroundColor = selectedBackgroundColor
|
||||
|
||||
item.state = .Normal
|
||||
return item
|
||||
}
|
||||
|
||||
fileprivate lazy var scrollView : UIScrollView = {
|
||||
let scrollView = UIScrollView()
|
||||
scrollView.alwaysBounceHorizontal = true
|
||||
scrollView.alwaysBounceVertical = false
|
||||
scrollView.showsHorizontalScrollIndicator = false
|
||||
scrollView.showsVerticalScrollIndicator = false
|
||||
scrollView.bounces = false
|
||||
scrollView.backgroundColor = UIColor.clear
|
||||
return scrollView
|
||||
}()
|
||||
fileprivate lazy var contentView = UIView()
|
||||
|
||||
fileprivate lazy var selectedBackgroundView: SelectedBackgroundView = {
|
||||
let selectedBackgroundView = SelectedBackgroundView.init()
|
||||
|
||||
return selectedBackgroundView
|
||||
}()
|
||||
|
||||
|
||||
|
||||
|
||||
fileprivate var itemViews = [SegmentItemView]()
|
||||
|
||||
fileprivate var numberOfSegments : Int {
|
||||
return itemViews.count
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
|
||||
setupViews()
|
||||
|
||||
scrollToPointWhenIndexChanged = scrollView.center
|
||||
}
|
||||
|
||||
fileprivate func setupViews() {
|
||||
|
||||
addSubview(scrollView)
|
||||
scrollView.addSubview(contentView)
|
||||
contentView.addSubview(selectedBackgroundView)
|
||||
|
||||
|
||||
scrollView.frame = bounds
|
||||
contentView.frame = scrollView.bounds
|
||||
|
||||
scrollView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
|
||||
|
||||
addTapGesture()
|
||||
}
|
||||
|
||||
private func addTapGesture() {
|
||||
let tap = UITapGestureRecognizer(target: self, action: #selector(didTapSegement(tapGesture:)))
|
||||
|
||||
contentView.addGestureRecognizer(tap)
|
||||
}
|
||||
|
||||
@objc private func didTapSegement(tapGesture:UITapGestureRecognizer) {
|
||||
let index = selectedTargetIndex(gesture: tapGesture)
|
||||
move(to: index)
|
||||
}
|
||||
|
||||
open func move(to index:Int){
|
||||
move(to: index, animated: true)
|
||||
}
|
||||
|
||||
open func move(to index:Int, animated:Bool) {
|
||||
|
||||
let position = centerX(with: index) - 10
|
||||
if animated {
|
||||
UIView.animate(withDuration: 0.2, animations: {
|
||||
self.selectedBackgroundView.center.x = position
|
||||
}) { (finished) in
|
||||
self.delegate?.didSelected?(segement: self, index: index)
|
||||
self.selectedIndex = index
|
||||
|
||||
if self.autoScrollWhenIndexChange {
|
||||
self.scrollItemToPoint(index: index, point: self.scrollToPointWhenIndexChanged!)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// 中心点
|
||||
self.selectedBackgroundView.center.x = position
|
||||
self.selectedBackgroundView.center.y = self.center.y
|
||||
|
||||
delegate?.didSelected?(segement: self, index: index)
|
||||
selectedIndex = index
|
||||
|
||||
if autoScrollWhenIndexChange {
|
||||
scrollItemToPoint(index: index, point: scrollToPointWhenIndexChanged!)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
fileprivate func currentItemX(index:Int) -> CGFloat {
|
||||
if autoAdjustWidth {
|
||||
var x:CGFloat = 0.0
|
||||
for i in 0..<index {
|
||||
x += segmentWidth(index: i)
|
||||
}
|
||||
return x
|
||||
}
|
||||
return segementWidth() * CGFloat(index)
|
||||
}
|
||||
|
||||
fileprivate func centerX(with index:Int) -> CGFloat {
|
||||
if autoAdjustWidth {
|
||||
return currentItemX(index: index) + segmentWidth(index: index)*0.5
|
||||
}
|
||||
return (CGFloat(index) + 0.5)*segementWidth()
|
||||
}
|
||||
|
||||
private func selectedTargetIndex(gesture: UIGestureRecognizer) -> Int {
|
||||
let location = gesture.location(in: contentView)
|
||||
var index = 0
|
||||
|
||||
if autoAdjustWidth {
|
||||
for (i,itemView) in itemViews.enumerated() {
|
||||
if itemView.frame.contains(location) {
|
||||
index = i
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
index = Int(location.x / segmentWidth(index: selectedIndex))
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
index = 0
|
||||
}
|
||||
if index > numberOfSegments - 1 {
|
||||
index = numberOfSegments - 1
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
private func scrollItemToCenter(index : Int) {
|
||||
scrollItemToPoint(index: index, point: CGPoint(x: scrollView.bounds.size.width * 0.5, y: 0))
|
||||
}
|
||||
|
||||
private func scrollItemToPoint(index : Int,point:CGPoint) {
|
||||
|
||||
let currentX = currentItemX(index: index)
|
||||
|
||||
let scrollViewWidth = scrollView.bounds.size.width
|
||||
|
||||
var scrollX = currentX - point.x + segmentWidth(index: index) * 0.5
|
||||
|
||||
let maxScrollX = scrollView.contentSize.width - scrollViewWidth
|
||||
|
||||
if scrollX > maxScrollX {
|
||||
scrollX = maxScrollX
|
||||
}
|
||||
if scrollX < 0.0 {
|
||||
scrollX = 0.0
|
||||
}
|
||||
|
||||
scrollView.setContentOffset(CGPoint(x: scrollX, y: 0.0), animated: true)
|
||||
}
|
||||
|
||||
required public init?(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
open override func layoutSubviews() {
|
||||
super.layoutSubviews()
|
||||
|
||||
guard itemViews.count > 0 else {
|
||||
return
|
||||
}
|
||||
|
||||
var x:CGFloat = 0.0
|
||||
let y:CGFloat = 0.0
|
||||
var width:CGFloat = segmentWidth(index: selectedIndex)
|
||||
let height:CGFloat = bounds.size.height
|
||||
|
||||
var contentWidth:CGFloat = 5.0
|
||||
|
||||
|
||||
selectedBackgroundView.frame = CGRect(x: currentItemX(index: selectedIndex) + SegmentControlSetting.selectedViewpadding, y: height - 22, width: 36, height: selectedViewHeight())
|
||||
|
||||
|
||||
self.selectedBackgroundView.center.x = centerX(with: 0) - 10
|
||||
|
||||
|
||||
for (index,item) in itemViews.enumerated() {
|
||||
x = contentWidth
|
||||
width = segmentWidth(index: index)
|
||||
item.frame = CGRect(x: x, y: y, width: width, height: height)
|
||||
|
||||
contentWidth += width
|
||||
}
|
||||
contentView.frame = CGRect(x: 0.0, y: 0.0, width: contentWidth, height: height)
|
||||
scrollView.contentSize = contentView.bounds.size
|
||||
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
//
|
||||
// Player.swift
|
||||
// IndieMusic
|
||||
//
|
||||
// Created by WenLei on 2023/11/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RxDataSources
|
||||
|
||||
struct PlayerLyrics: Codable {
|
||||
let lyrics: String?
|
||||
}
|
||||
|
||||
|
||||
|
||||
struct PlayerLyricsSection {
|
||||
var items: [PlayerLyrics]
|
||||
}
|
||||
|
||||
extension PlayerLyricsSection: SectionModelType {
|
||||
typealias Item = PlayerLyrics
|
||||
|
||||
init(original: PlayerLyricsSection, items: [Item]) {
|
||||
self = original
|
||||
self.items = items
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
//
|
||||
// PlayerViewModel.swift
|
||||
// IndieMusic
|
||||
//
|
||||
// Created by WenLei on 2023/11/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import RxSwift
|
||||
import RxCocoa
|
||||
|
||||
class PlayerViewModel: ViewModel, ViewModelType {
|
||||
|
||||
struct Input {
|
||||
}
|
||||
|
||||
struct Output {
|
||||
let items: BehaviorRelay<[PlayerLyricsSection]>
|
||||
|
||||
}
|
||||
|
||||
let items = BehaviorRelay<[PlayerLyricsSection]>.init(value: [])
|
||||
|
||||
func transform(input: Input) -> Output {
|
||||
|
||||
let lyrics = PlayerLyrics.init(lyrics: "1233211232")
|
||||
let section = PlayerLyricsSection.init(items: [lyrics, lyrics, lyrics, lyrics, lyrics, lyrics, lyrics])
|
||||
|
||||
items.accept([section])
|
||||
|
||||
return Output.init(items: items)
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in new issue