Finish building the player interface

dev
wenlei 1 year ago
parent 36d13b2769
commit 5604910471

@ -14,6 +14,9 @@
774399A62AFE036A006F8EEA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774399A52AFE036A006F8EEA /* PlayerView.swift */; }; 774399A62AFE036A006F8EEA /* PlayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774399A52AFE036A006F8EEA /* PlayerView.swift */; };
774399A82AFE28BA006F8EEA /* BlurEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774399A72AFE28BA006F8EEA /* BlurEffectView.swift */; }; 774399A82AFE28BA006F8EEA /* BlurEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774399A72AFE28BA006F8EEA /* BlurEffectView.swift */; };
774399AA2AFE3170006F8EEA /* PaddingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774399A92AFE3170006F8EEA /* PaddingLabel.swift */; }; 774399AA2AFE3170006F8EEA /* PaddingLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774399A92AFE3170006F8EEA /* PaddingLabel.swift */; };
774A17F32B0459C900F56DF1 /* PlayerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774A17F22B0459C900F56DF1 /* PlayerViewModel.swift */; };
774A17F52B045F1C00F56DF1 /* Player.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774A17F42B045F1C00F56DF1 /* Player.swift */; };
774A17F72B04932100F56DF1 /* SegmentControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = 774A17F62B04932100F56DF1 /* SegmentControl.swift */; };
778B8A212AF8E36D0034AFD4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8A202AF8E36D0034AFD4 /* AppDelegate.swift */; }; 778B8A212AF8E36D0034AFD4 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8A202AF8E36D0034AFD4 /* AppDelegate.swift */; };
778B8A232AF8E36D0034AFD4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8A222AF8E36D0034AFD4 /* SceneDelegate.swift */; }; 778B8A232AF8E36D0034AFD4 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 778B8A222AF8E36D0034AFD4 /* SceneDelegate.swift */; };
778B8A282AF8E36D0034AFD4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 778B8A262AF8E36D0034AFD4 /* Main.storyboard */; }; 778B8A282AF8E36D0034AFD4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 778B8A262AF8E36D0034AFD4 /* Main.storyboard */; };
@ -101,6 +104,9 @@
774399A52AFE036A006F8EEA /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; }; 774399A52AFE036A006F8EEA /* PlayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerView.swift; sourceTree = "<group>"; };
774399A72AFE28BA006F8EEA /* BlurEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurEffectView.swift; sourceTree = "<group>"; }; 774399A72AFE28BA006F8EEA /* BlurEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurEffectView.swift; sourceTree = "<group>"; };
774399A92AFE3170006F8EEA /* PaddingLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingLabel.swift; sourceTree = "<group>"; }; 774399A92AFE3170006F8EEA /* PaddingLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaddingLabel.swift; sourceTree = "<group>"; };
774A17F22B0459C900F56DF1 /* PlayerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlayerViewModel.swift; sourceTree = "<group>"; };
774A17F42B045F1C00F56DF1 /* Player.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Player.swift; sourceTree = "<group>"; };
774A17F62B04932100F56DF1 /* SegmentControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SegmentControl.swift; sourceTree = "<group>"; };
778B8A1D2AF8E36D0034AFD4 /* IndieMusic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IndieMusic.app; sourceTree = BUILT_PRODUCTS_DIR; }; 778B8A1D2AF8E36D0034AFD4 /* IndieMusic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = IndieMusic.app; sourceTree = BUILT_PRODUCTS_DIR; };
778B8A202AF8E36D0034AFD4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; }; 778B8A202AF8E36D0034AFD4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
778B8A222AF8E36D0034AFD4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; }; 778B8A222AF8E36D0034AFD4 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = "<group>"; };
@ -201,6 +207,7 @@
7743999D2AFA18C3006F8EEA /* PlayerViewController.swift */, 7743999D2AFA18C3006F8EEA /* PlayerViewController.swift */,
7743999F2AFA1968006F8EEA /* PlayerTabBar.swift */, 7743999F2AFA1968006F8EEA /* PlayerTabBar.swift */,
774399A52AFE036A006F8EEA /* PlayerView.swift */, 774399A52AFE036A006F8EEA /* PlayerView.swift */,
774A17F22B0459C900F56DF1 /* PlayerViewModel.swift */,
); );
path = Player; path = Player;
sourceTree = "<group>"; sourceTree = "<group>";
@ -295,6 +302,7 @@
778B8AB22AF8ED270034AFD4 /* WebViewController.swift */, 778B8AB22AF8ED270034AFD4 /* WebViewController.swift */,
774399A72AFE28BA006F8EEA /* BlurEffectView.swift */, 774399A72AFE28BA006F8EEA /* BlurEffectView.swift */,
774399A92AFE3170006F8EEA /* PaddingLabel.swift */, 774399A92AFE3170006F8EEA /* PaddingLabel.swift */,
774A17F62B04932100F56DF1 /* SegmentControl.swift */,
); );
path = Common; path = Common;
sourceTree = "<group>"; sourceTree = "<group>";
@ -343,6 +351,7 @@
778B8A8C2AF8ECF20034AFD4 /* AudioTrack.swift */, 778B8A8C2AF8ECF20034AFD4 /* AudioTrack.swift */,
778B8A892AF8ECF20034AFD4 /* EmptyModel.swift */, 778B8A892AF8ECF20034AFD4 /* EmptyModel.swift */,
778B8A8A2AF8ECF20034AFD4 /* Token.swift */, 778B8A8A2AF8ECF20034AFD4 /* Token.swift */,
774A17F42B045F1C00F56DF1 /* Player.swift */,
); );
path = Models; path = Models;
sourceTree = "<group>"; sourceTree = "<group>";
@ -738,6 +747,7 @@
778B8ABE2AF8ED280034AFD4 /* ViewModel.swift in Sources */, 778B8ABE2AF8ED280034AFD4 /* ViewModel.swift in Sources */,
774399A82AFE28BA006F8EEA /* BlurEffectView.swift in Sources */, 774399A82AFE28BA006F8EEA /* BlurEffectView.swift in Sources */,
778B8A852AF8ECE50034AFD4 /* SearchViewController.swift in Sources */, 778B8A852AF8ECE50034AFD4 /* SearchViewController.swift in Sources */,
774A17F52B045F1C00F56DF1 /* Player.swift in Sources */,
778B8AAB2AF8ED0E0034AFD4 /* UIView+Borders.swift in Sources */, 778B8AAB2AF8ED0E0034AFD4 /* UIView+Borders.swift in Sources */,
778B8AC12AF8ED280034AFD4 /* TableView.swift in Sources */, 778B8AC12AF8ED280034AFD4 /* TableView.swift in Sources */,
778B8A9C2AF8ECFC0034AFD4 /* LogManager.swift in Sources */, 778B8A9C2AF8ECFC0034AFD4 /* LogManager.swift in Sources */,
@ -755,8 +765,10 @@
778B8A232AF8E36D0034AFD4 /* SceneDelegate.swift in Sources */, 778B8A232AF8E36D0034AFD4 /* SceneDelegate.swift in Sources */,
778B8A6F2AF8ECD30034AFD4 /* APIConfig.swift in Sources */, 778B8A6F2AF8ECD30034AFD4 /* APIConfig.swift in Sources */,
778B8A8E2AF8ECF20034AFD4 /* Artist.swift in Sources */, 778B8A8E2AF8ECF20034AFD4 /* Artist.swift in Sources */,
774A17F32B0459C900F56DF1 /* PlayerViewModel.swift in Sources */,
778B8A6D2AF8ECD30034AFD4 /* Observable+Operators.swift in Sources */, 778B8A6D2AF8ECD30034AFD4 /* Observable+Operators.swift in Sources */,
778B8ABB2AF8ED280034AFD4 /* WebViewController.swift in Sources */, 778B8ABB2AF8ED280034AFD4 /* WebViewController.swift in Sources */,
774A17F72B04932100F56DF1 /* SegmentControl.swift in Sources */,
778B8A712AF8ECD30034AFD4 /* Api.swift in Sources */, 778B8A712AF8ECD30034AFD4 /* Api.swift in Sources */,
778B8A812AF8ECE50034AFD4 /* HomeTabBarViewModel.swift in Sources */, 778B8A812AF8ECE50034AFD4 /* HomeTabBarViewModel.swift in Sources */,
778B8AC02AF8ED280034AFD4 /* ViewController.swift in Sources */, 778B8AC02AF8ED280034AFD4 /* ViewController.swift in Sources */,

@ -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
}
}

@ -37,6 +37,8 @@ class HomeViewController: ViewController {
} }
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
let viewModel = PlayerViewModel.init(provider: viewModel!.provider)
let play = PlayerViewController.init(viewModel: viewModel, navigator: navigator) let play = PlayerViewController.init(viewModel: viewModel, navigator: navigator)
play.modalPresentationStyle = .custom play.modalPresentationStyle = .custom
self.present(play, animated: true) self.present(play, animated: true)

@ -15,10 +15,14 @@ class PlayerViewTopBar: UIView {
return dropButton return dropButton
}() }()
lazy var segementView: UIView = { lazy var segmentControl: SegmentControl = {
let segementView = UIView.init() let segmentControl = SegmentControl.init()
segmentControl.items = ["歌曲", "歌词"]
segmentControl.font = UIFont.systemFont(ofSize: 15)
segmentControl.selectedFont = UIFont.systemFont(ofSize: 15)
return segementView
return segmentControl
}() }()
lazy var moreButton: UIButton = { lazy var moreButton: UIButton = {
@ -41,7 +45,7 @@ class PlayerViewTopBar: UIView {
func makeUI() { func makeUI() {
addSubview(dropButton) addSubview(dropButton)
addSubview(segementView) addSubview(segmentControl)
addSubview(moreButton) addSubview(moreButton)
} }
@ -60,10 +64,12 @@ class PlayerViewTopBar: UIView {
make.centerY.equalTo(self) make.centerY.equalTo(self)
} }
segementView.snp.makeConstraints { make in segmentControl.snp.makeConstraints { make in
make.left.equalTo(dropButton.snp.right).offset(10) // make.left.equalTo(dropButton.snp.right).offset(10)
make.right.equalTo(moreButton.snp.left).offset(-10) // make.right.equalTo(moreButton.snp.left).offset(-10)
make.width.equalTo(145)
make.top.equalTo(self) make.top.equalTo(self)
make.centerX.equalTo(self)
make.bottom.equalTo(self) make.bottom.equalTo(self)
} }
@ -164,7 +170,9 @@ class PlayerControlView: UIView {
} }
protocol PlayerScrollViewDelegate: NSObjectProtocol {
func scrollViewDidChange(index: Int)
}
class PlayerScrollView: UIScrollView { class PlayerScrollView: UIScrollView {
var containerView: UIView = { var containerView: UIView = {
@ -180,13 +188,16 @@ class PlayerScrollView: UIScrollView {
return playerInfoView return playerInfoView
}() }()
var lyricsView: UIView = { var playerLyricsView: PlayerLyricsView = {
let lyricsView = UIView.init() let playerLyricsView = PlayerLyricsView.init()
return lyricsView return playerLyricsView
}() }()
weak var scrollDelegate: PlayerScrollViewDelegate?
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
@ -198,10 +209,14 @@ class PlayerScrollView: UIScrollView {
} }
func makeUI() { func makeUI() {
showsHorizontalScrollIndicator = false
isPagingEnabled = true
delegate = self
addSubview(containerView) addSubview(containerView)
containerView.addSubview(playerInfoView) containerView.addSubview(playerInfoView)
containerView.addSubview(lyricsView) containerView.addSubview(playerLyricsView)
} }
override func layoutSubviews() { override func layoutSubviews() {
@ -220,20 +235,202 @@ class PlayerScrollView: UIScrollView {
make.width.equalTo(BaseDimensions.screenWidth) make.width.equalTo(BaseDimensions.screenWidth)
} }
lyricsView.snp.makeConstraints { make in playerLyricsView.snp.makeConstraints { make in
make.left.equalTo(playerInfoView.snp.right) make.left.equalTo(playerInfoView.snp.right)
make.top.equalTo(containerView) make.top.equalTo(containerView)
make.bottom.equalTo(containerView) make.bottom.equalTo(containerView)
make.right.equalTo(containerView) make.right.equalTo(containerView)
make.width.equalTo(BaseDimensions.screenWidth)
}
}
func moveToPage(page: Int, animated: Bool) {
let width = frame.size.width
let offset = CGPoint(x: Int(width) * page, y: 0)
setContentOffset(offset, animated: animated)
}
}
extension PlayerScrollView: UIScrollViewDelegate {
// func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {
// let pageWidth = BaseDimensions.screenWidth //
// let targetXContentOffset = targetContentOffset.pointee.x
// let contentWidth = scrollView.contentSize.width
// var newPage = targetXContentOffset / pageWidth
//
// if velocity.x == 0 { //
// newPage = floor((targetXContentOffset - pageWidth / 2) / pageWidth) + 1.0
// } else {
// newPage = velocity.x > 0 ? ceil(newPage) : floor(newPage)
// }
//
// //
// newPage = max(0, min(newPage, ceil(contentWidth / pageWidth) - 1))
// targetContentOffset.pointee.x = CGFloat(newPage * pageWidth)
// }
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
let width = scrollView.frame.size.width
let pageIndex = Int(round(scrollView.contentOffset.x / width))
scrollDelegate?.scrollViewDidChange(index: pageIndex)
}
}
class PlayerLyricsView: UIView {
lazy var tableView: UITableView = {
let tableView = UITableView.init()
tableView.backgroundColor = .clear
return tableView
}()
lazy var audioTrackView: UIView = {
let artistInfoView = UIView.init()
return artistInfoView
}()
lazy var audioTrackLabel: UILabel = {
let audioTrackLabel = UILabel.init()
audioTrackLabel.font = UIFont.systemFont(ofSize: 20)
audioTrackLabel.textColor = UIColor.init(hex: 0xFFFFFF)
return audioTrackLabel
}()
lazy var artistLabel: UILabel = {
let artistLabel = UILabel.init()
artistLabel.font = UIFont.systemFont(ofSize: 12)
artistLabel.textColor = UIColor.init(hex: 0xFFFFFF, alpha: 0.6)
return artistLabel
}()
override init(frame: CGRect) {
super.init(frame: frame)
makeUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
// audioTrackView.backgroundColor = .red
// tableView.backgroundColor = .blue
addSubview(audioTrackView)
addSubview(tableView)
audioTrackView.addSubview(audioTrackLabel)
audioTrackView.addSubview(artistLabel)
audioTrackLabel.text = "test"
artistLabel.text = "tester"
}
override func layoutSubviews() {
super.layoutSubviews()
audioTrackLabel.snp.makeConstraints { make in
make.left.equalTo(audioTrackView).offset(18)
make.right.equalTo(audioTrackView).offset(-18)
make.top.equalTo(audioTrackView).offset(30)
} }
artistLabel.snp.makeConstraints { make in
make.left.equalTo(audioTrackView).offset(18)
make.right.equalTo(audioTrackView).offset(-18)
make.top.equalTo(audioTrackLabel.snp.bottom)
}
audioTrackView.snp.makeConstraints { make in
make.left.equalTo(self)
make.right.equalTo(self)
make.top.equalTo(self)
make.height.equalTo(100)
}
tableView.snp.makeConstraints { make in
make.left.equalTo(self)
make.right.equalTo(self)
make.top.equalTo(audioTrackView.snp.bottom)
make.bottom.equalTo(self)
}
} }
}
class PlayerLyricsCell: UITableViewCell {
var lyricsLabel: UILabel = {
let lyricsLabel = UILabel.init()
lyricsLabel.font = UIFont.systemFont(ofSize: 18)
lyricsLabel.textColor = .init(hex: 0xFFFFFF, alpha: 1)
return lyricsLabel
}()
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
makeUI()
autoLayout()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func makeUI() {
backgroundColor = .clear
// contentView.backgroundColor = .clear
contentView.addSubview(lyricsLabel)
}
func autoLayout() {
lyricsLabel.snp.makeConstraints { make in
make.left.equalTo(contentView).offset(18)
make.right.equalTo(contentView).offset(-18)
make.top.equalTo(contentView).offset(18)
make.bottom.equalTo(contentView).offset(-18)
}
}
} }
class PlayerInfoView: UIView { class PlayerInfoView: UIView {
var coverView: UIImageView = { var coverView: UIImageView = {
let coverView = UIImageView.init() let coverView = UIImageView.init()

@ -6,6 +6,9 @@
// //
import UIKit import UIKit
import RxSwift
import RxCocoa
import RxDataSources
class PlayerViewController: ViewController { class PlayerViewController: ViewController {
@ -20,13 +23,13 @@ class PlayerViewController: ViewController {
playerViewTopBar.setContentHuggingPriority(.required, for: .vertical) playerViewTopBar.setContentHuggingPriority(.required, for: .vertical)
playerViewTopBar.setContentCompressionResistancePriority(.required, for: .vertical) playerViewTopBar.setContentCompressionResistancePriority(.required, for: .vertical)
return playerViewTopBar return playerViewTopBar
}() }()
var playerScrollView: PlayerScrollView = { var playerScrollView: PlayerScrollView = {
let playerScrollView = PlayerScrollView.init() let playerScrollView = PlayerScrollView.init()
return playerScrollView return playerScrollView
}() }()
@ -44,6 +47,8 @@ class PlayerViewController: ViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
} }
override func viewWillAppear(_ animated: Bool) { override func viewWillAppear(_ animated: Bool) {
@ -55,6 +60,10 @@ class PlayerViewController: ViewController {
override func makeUI() { override func makeUI() {
super.makeUI() super.makeUI()
playerScrollView.scrollDelegate = self
playerViewTopBar.segmentControl.delegate = self
view.backgroundColor = .white view.backgroundColor = .white
view.addSubview(blurEffectView) view.addSubview(blurEffectView)
@ -67,6 +76,22 @@ class PlayerViewController: ViewController {
override func bindViewModel() { override func bindViewModel() {
super.bindViewModel() super.bindViewModel()
playerScrollView.playerLyricsView.tableView.register(PlayerLyricsCell.self, forCellReuseIdentifier: "PlayerLyricsCell")
guard let viewModel = viewModel as? PlayerViewModel else { return }
let input = PlayerViewModel.Input.init()
let output = viewModel.transform(input: input)
let dataSource = PlayerViewController.dataSource { cell, oneStepInfoModel in
}
output.items.bind(to: playerScrollView.playerLyricsView.tableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)
} }
override func viewDidLayoutSubviews() { override func viewDidLayoutSubviews() {
@ -103,3 +128,31 @@ class PlayerViewController: ViewController {
self.dismiss(animated: true) self.dismiss(animated: true)
} }
} }
extension PlayerViewController {
static func dataSource(_ buttonTapHandler: @escaping (PlayerLyricsCell, PlayerLyrics) -> Void) -> RxTableViewSectionedReloadDataSource<PlayerLyricsSection> {
return RxTableViewSectionedReloadDataSource { dataSource, tableView, indexPath, item in
let cell: PlayerLyricsCell = tableView.dequeueReusableCell(withIdentifier: "PlayerLyricsCell", for: indexPath) as! PlayerLyricsCell
cell.lyricsLabel.text = item.lyrics
return cell
}
}
}
extension PlayerViewController: SegmentControlDelegate {
func didSelected(segement: SegmentControl, index: Int) {
playerScrollView.moveToPage(page: index, animated: true)
}
}
extension PlayerViewController: PlayerScrollViewDelegate {
func scrollViewDidChange(index: Int) {
playerViewTopBar.segmentControl.move(to: index, animated: true)
}
}

@ -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)
}
}

@ -21,6 +21,7 @@ target 'IndieMusic' do
pod 'RxCocoa', '6.2.0' pod 'RxCocoa', '6.2.0'
pod 'Moya/RxSwift', '~> 15.0' pod 'Moya/RxSwift', '~> 15.0'
pod 'RxTheme', '~> 6.0' pod 'RxTheme', '~> 6.0'
pod 'RxDataSources'
target 'IndieMusicTests' do target 'IndieMusicTests' do
inherit! :search_paths inherit! :search_paths

Loading…
Cancel
Save