IOS 26 实现歌单详情(UITableView)列表 ③

news2024/9/19 10:49:01

 歌单详情完整效果

歌单列表分组头部效果

 

本节是在文章 IOS 25 实现歌单详情(UITableView)列表② 的基础上,实现歌单列表分组头部View。当歌单列表滑动头部View至顶部时,头部View不会因列表滑动而消失,会一直显示在顶部。故,使用UITableViewHeaderFooterView 来实现。

实现逻辑

将歌单详情分为两组,图1为1组,图2为1组,每组都包含头部View(UITableViewHeaderFooterView);图1不需要头部View,则设置头部View隐藏,图2头部View在滚动歌单列表到顶部时,头部View会固定在顶部不消失。

歌单列表头部View实现

实现流程:
1.创建UITableViewHeaderFooterView,及在使用UITableView的Controller控制器上注册;

2.获取data分组数据,并调用UITableView的reloadData(),将数据更新到列表;

3.将data的Item数据绑定UITableView的每一个Section。

1)创建和注册HeaderFooterView

从效果图上面可以看出,歌单列表头部View由一个水平方向布局包含UIImageView和UITableView来实现。

自定义SongGroupHeaderView,继承自BaseTableViewHeaderFooterView,设置TGLinearLayout为水平方向。


class SongGroupHeaderView : BaseTableViewHeaderFooterView{
    
    static let NAME = "SongGroupHeaderView"
    
    override func initViews() {
        super.initViews()
        backgroundColor = .colorBackgroundLight
        contentView.backgroundColor = .colorBackgroundLight
        
        container.tg_gravity = TGGravity.vert.center
        
        container.tg_padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: PADDING_SMALL)

    }
    
    override func getContainerOrientation() -> TGOrientation {
        return .horz
    }
    
}

添加图标、全部播放文本和歌曲数量等布局,添加头部点击事件,并绑定布局数据。

//
//  SongGroupHeaderView.swift
//  MyCloudMusic
//
//  Created by jin on 2024/9/13.
//

import UIKit
import TangramKit

class SongGroupHeaderView : BaseTableViewHeaderFooterView{
    
    static let NAME = "SongGroupHeaderView"
    
    var countView:UILabel!
    
    /// 播放所有点击
    var playAllClick:(()->Void)!
    
    override func initViews() {
        super.initViews()
        backgroundColor = .colorBackgroundLight
        contentView.backgroundColor = .colorBackgroundLight
        
        container.tg_gravity = TGGravity.vert.center
        
        container.tg_padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: PADDING_SMALL)
        
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(onPlayAllTouchEvent(_:)))

        //将触摸事件添加到当前view
        container.addGestureRecognizer(tapGestureRecognizer)
        
        //左侧容器
        let leftContainer = TGRelativeLayout()
        leftContainer.tg_width.equal(50)
        leftContainer.tg_height.equal(50)
        container.addSubview(leftContainer)
        
        //图标
        let iconView = UIImageView()
        iconView.tg_width.equal(30)
        iconView.tg_height.equal(30)
        iconView.tg_centerX.equal(0)
        iconView.tg_centerY.equal(0)
        iconView.image = R.image.playCircle()?.withTintColor()
        iconView.tintColor = .colorPrimary
        
        leftContainer.addSubview(iconView)
        
        //播放全部提示文本
        let titleView = UILabel()
        titleView.tg_width.equal(.wrap)
        titleView.tg_height.equal(.wrap)
        titleView.text = R.string.localizable.playAll()
        titleView.font = UIFont.boldSystemFont(ofSize: TEXT_LARGE2)
        titleView.textColor = .colorOnSurface
        container.addSubview(titleView)
        
        //数量
        countView = UILabel()
        countView.tg_width.equal(.fill)
        countView.tg_height.equal(.wrap)
        countView.text = "0"
        countView.textAlignment = .left
        countView.font = UIFont.systemFont(ofSize: TEXT_MEDDLE)
        countView.textColor = .black80
        container.addSubview(countView)
        
        //下载按钮
        let downloadButton = ViewFactoryUtil.button(image:R.image.arrowCircleDown()!.withTintColor())
        downloadButton.tintColor = .colorOnSurface
        downloadButton.tg_width.equal(50)
        downloadButton.tg_height.equal(50)
        container.addSubview(downloadButton)
        
        //多选按钮
        let multiSelectButton = ViewFactoryUtil.button(image:R.image.moreVerticalDot()!.withTintColor())
        multiSelectButton.tintColor = .colorOnSurface
        multiSelectButton.tg_width.equal(50)
        multiSelectButton.tg_height.equal(50)
        container.addSubview(multiSelectButton)
    }
    
    override func getContainerOrientation() -> TGOrientation {
        return .horz
    }
    
    @objc func onPlayAllTouchEvent(_ recognizer:UITapGestureRecognizer) {
        playAllClick()
    }
    
    func bind(_ data:SongGroupData) {
        countView.text = "(\(data.datum.count))"
    }
}

BaseTableViewHeaderFooterView实现 

//
//  BaseTableViewHeaderFooterView.swift
//  TableViewHeaderFooterView基类
//
//  Created by jin on 2024/9/13.
//

import TangramKit
import UIKit

class BaseTableViewHeaderFooterView: UITableViewHeaderFooterView {
    // 对于需要动态评估高度的UITableViewCell来说可以把布局视图暴露出来。用于高度评估和边界线处理。以及事件处理的设置。
    var container: TGBaseLayout!
    
    override init(reuseIdentifier: String?) {
        super.init(reuseIdentifier: reuseIdentifier)
        innerInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        innerInit()
    }
    
    func innerInit() {
        initViews()
        initDatum()
        initListeners()
    }
    
    /// 找控件
    func initViews() {
        backgroundColor = .clear
        contentView.backgroundColor = .clear
        
        container = TGLinearLayout(getContainerOrientation())
        container.tg_width.equal(.fill)
        container.tg_height.equal(.wrap)
        contentView.addSubview(container)
    }
    
    /// 设置数据
    func initDatum() {}
    
    /// 设置监听器
    func initListeners() {}

    /// 获取根容器布局方向
    func getContainerOrientation() -> TGOrientation {
        return .vert
    }
    
    /// 使用MyLayout后,让item自动计算高度,要重写该方法
    override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
        return container.systemLayoutSizeFitting(targetSize)
    }
}

在SheetDetailController控制器,注册SongGroupHeaderView

class SheetDetailController: BaseTitleController {
    
    override func initViews() {
        super.initViews()
        title = R.string.localizable.sheet()
        
        // 注册单曲
        tableView.register(SongItemCell.self, forCellReuseIdentifier: Constant.CELL)
        tableView.register(SheetInfoCell.self, forCellReuseIdentifier: SheetInfoCell.NAME)
        
        //注册section
        tableView.register(SongGroupHeaderView.self, forHeaderFooterViewReuseIdentifier: SongGroupHeaderView.NAME)
        
        tableView.bounces = false
    }
}
2)获取data分组列表数据

定义分组列表数据模型SongGroupData

//
//  SongGroupData.swift
//  音乐分组模型
//
//  Created by jin on 2024/9/13.
//

import Foundation

class SongGroupData: BaseModel {
    var datum:Array<Any>!
}

请求歌单详情接口获取歌单详情里的歌曲列表数据,组装成分组数据,更新tableView.reloadData()

class SheetDetailController: BaseTitleController {
    
    override func initDatum() {
        super.initDatum()
        loadData()
    }
    
    func loadData() {
        DefaultRepository.shared
            .sheetDetail(id)
            .subscribeSuccess { [weak self] data in
                self?.show(data.data!)
            }.disposed(by: rx.disposeBag)
    }
    
    func show(_ data: Sheet) {
        self.data = data

        //第一组
        var groupData = SongGroupData()
        groupData.datum = [data]
        datum.append(groupData)
        
        //第二组
        if let r = data.songs {
            if !r.isEmpty {
                //有音乐才设置

                //设置数据
                groupData = SongGroupData()
                groupData.datum = r
                datum.append(groupData)
                
                superFooterContainer.backgroundColor = .colorLightWhite
            }
        }
        
        tableView.reloadData()
    }
}
3)Item数据绑定Section

SheetDetailController控制器扩展父类的实现UITableViewDataSource:

1)实现numberOfSections方法,返回分组数量,即有多少组;

2)重写numberOfRowsInSection方法,返回每个分组内的列表长度,即当前组有多少个; 

3)实现viewForHeaderInSection方法,创建对应的HeaderView,并将分组数据绑定到HeaderView。

4)重写cellForRowAt方法,创建对应的Cell,并将分组数据内的列表的Item数据绑定到Cell。

extension SheetDetailController {
    
    /// 有多少组
    func numberOfSections(in tableView: UITableView) -> Int {
        return datum.count
    }
    
    /// 当前组有多少个
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        let data = datum[section] as! SongGroupData
        return data.datum.count
    }
    
    /// 返回section view
    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        //取出组数据
        let groupData = datum[section] as! SongGroupData
        //获取header
        let header  = tableView.dequeueReusableHeaderFooterView(withIdentifier: SongGroupHeaderView.NAME) as! SongGroupHeaderView
        header.bind(groupData)
        
        header.playAllClick = {[weak self] in
            let groupData = self?.datum[1] as! SongGroupData
            self?.play(groupData.datum[0] as! Song)
        }
        
        return header
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let groupData = datum[indexPath.section] as! SongGroupData
        let data = groupData.datum[indexPath.row]

        let type = typeForItemAtData(data)
        
        switch type {
        case .sheet:
            let cell = tableView.dequeueReusableCell(withIdentifier: SheetInfoCell.NAME, for: indexPath) as! SheetInfoCell
            cell.bind(data as! Sheet)
            return cell
        default:
            let cell = tableView.dequeueReusableCell(withIdentifier: Constant.CELL, for: indexPath) as! SongItemCell
            cell.bind(data as! Song)
            cell.indexView.text = "\(indexPath.row + 1)"
            
            return cell
        }
    }
    
    /// header高度
    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        if section == 1 {
            return 50
        }
        
        //其他组不显示section
        return 0
    }
}

 至此,实现了歌单列表分组头部效果。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2146119.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2024.9.18

1.已知网址www.hqyj.com截取出网址的每一个部分 菜单栏中 ----> 虚拟机 -----> 设置 -----> 网络适配器 选择桥接模式 菜单栏中 ----> 编辑 -----> 虚拟网络编辑器 更改设置 将桥接改成自动 如果桥接连不上网 尝试还原默认设置后&#xff0c;在重新连接桥接…

微信小程序的学生选课系统--论文源码调试讲解

第二章 开发技术介绍 此次管理系统的关键技术和架构由B/S结构、java和mysql数据库&#xff0c;是本系统的关键开发技术&#xff0c;对系统的整体、数据库、功能模块、系统页面以及系统程序等设计进行了详细的研究与规划。 2.1 系统开发平台 在该在线微信小程序的学生选课系统…

动手学习RAG: 大模型向量模型微调 intfloat/e5-mistral-7b-instruct

动手学习RAG: 向量模型动手学习RAG: moka-ai/m3e 模型微调deepspeed与对比学习动手学习RAG&#xff1a;rerank模型微调实践 bge-reranker-v2-m3动手学习RAG&#xff1a;迟交互模型colbert微调实践 bge-m3动手学习RAG: 大模型向量模型微调 intfloat/e5-mistral-7b-instruct动手学…

JavaScript高级——内存溢出和内存泄漏

1、闭包的缺点与解决方法 &#xff08;1&#xff09;缺点&#xff1a;函数执行完后&#xff0c;函数内的局部变量没有释放&#xff0c;占用内存时间会变长。 容易造成内存泄漏。 &#xff08;2&#xff09;解决&#xff1a;能不用闭包就不用。 及时释放。 2、内存溢出 ① 一…

Linux进阶 查看系统进程

操作系统中进程的生命周期是: 创建进程,(服务启动或软件的启动)进行运行状态进程等待状态进行唤醒进程结束一般主要关注是进行中间的三种状态,三种状态之间装换关系如下: 1、就绪状态:表示进程已经做好了运行的准备状态,只要获得内存空间,就可以立即执行。 2、阻塞状态:…

Maya---机械模型制作

材质效果&#xff08;4&#xff09;_哔哩哔哩_bilibili 三角面 四边面 多边面 *游戏允许出现三角面和四边面 游戏中一般是低模&#xff08;几千个面&#xff09; 动漫及影视是高模 机械由单独零件组合而成&#xff0c;需独立制作 低面模型到高面模型 卡线是为了将模型保…

电脑怎么设置开机密码?3个方法迅速搞定!

电脑已经成为了我们日常办公与学习的重要工具&#xff0c;其中保存有很多重要且需保密的资料&#xff0c;为电脑设置开机密码则是保护资料安全的第一步。那么&#xff0c;电脑怎么设置开机密码呢&#xff1f;今天&#xff0c;小编就为大家介绍3个设置电脑开机密码的方法&#x…

深度学习对抗海洋赤潮危机!浙大GIS实验室提出ChloroFormer模型,可提前预警海洋藻类爆发

2014 年 8 月&#xff0c;美国俄亥俄州托莱多市超 50 万名居民突然收到市政府的一则紧急通知——不得擅自饮用自来水&#xff01; 水是人类生存的基本供给&#xff0c;此通告关系重大&#xff0c;发出后也引起了不小的恐慌。究其原因&#xff0c;其实是美国伊利湖爆发了大规模…

油烟机制造5G智能工厂物联数字孪生平台,推进制造业数字化转型

油烟机制造5G智能工厂物联数字孪生平台&#xff0c;是智能制造与信息技术的深度融合产物。数字孪生工业互联平台通过部署在工厂各个环节的传感器和设备&#xff0c;实时采集、分析和处理生产过程中的海量数据&#xff0c;构建出高度逼真的数字孪生模型。这一模型不仅能够真实反…

基于树莓派ubuntu20.04的ros-noetic小车

目录 一、小车的架构 1.1 总体的概述 1.2 驱动系统 1.3 控制系统 二、驱动系统开发 2.1 PC端Ubuntu20.04安装 2.2 树莓派Ubuntu20.04安装 2.3 PC端虚拟机设置静态IP 2.4 树莓派设置静态IP 2.5 树莓派启动ssh进行远程开发 2.5 arduino ide 开发环境搭建 2.5.1 PC…

深入探索Docker核心原理:从Libcontainer到runC的演化与实现

随着容器技术的发展&#xff0c;Docker从早期的Libcontainer逐步演化到runC&#xff0c;推动了容器运行时的标准化进程。Libcontainer是Docker容器的核心管理工具&#xff0c;而runC则在此基础上发展成为符合OCI&#xff08;Open Container Initiative&#xff09;标准的轻量级…

Vue常用PC端和移动端组件库、Element UI的基本使用(完整引入和按需引入)

目录 1. Vue常用PC端和移动端组件库2. Element UI的基本使用2.1 完整引入2.2 按需引入 1. Vue常用PC端和移动端组件库 提供常用的布局、按钮、输入框、下拉框等UI布局&#xff0c;以组件的形式提供。使用这些组件&#xff0c;结构、样式、交互就都有了 移动端常用UI组件库 Van…

windows10 修改默认输入法

右键桌面&#xff0c;选择个性化 左侧搜索 语言 选择编辑语言和键盘选项 点击键盘 默认替代输入法 选择你想要设置的。重启电脑。如下图

C语言18--头文件

头文件的作用 通常&#xff0c;一个常规的C语言程序会包含多个源码文件&#xff08;.c&#xff09;&#xff0c;当某些公共资源需要在各个源码文件中使用时&#xff0c;为了避免多次编写相同的代码&#xff0c;一般的做法是将这些大家都需要用到的公共资源放入头文件&#xff…

光学超表面在成像和传感中的应用

光学超表面已成为解决笨重光学元件所带来的限制&#xff0c;极具前景的解决方案。与传统的折射传播技术相比&#xff0c;它们提供了一种紧凑、高效的光操纵方法&#xff0c;可对相位、偏振和发射进行先进的控制。本文概述了光学超表面、它们在成像和传感技术中的各种应用以及这…

Broadcast:Android中实现组件与进程间通信

目录 一&#xff0c;Broadcast和BroadcastReceiver 1&#xff0c;简介 2&#xff0c;广播使用 二&#xff0c;静态注册和动态注册 三&#xff0c;无序广播和有序广播 1&#xff0c;有序广播的使用 2&#xff0c;有序广播的截断 3&#xff0c;有序广播的信息传递 四&am…

力扣(LeetCode)每日一题 1184. 公交站间的距离

题目链接https://leetcode.cn/problems/distance-between-bus-stops/description/?envTypedaily-question&envId2024-09-16 环形公交路线上有 n 个站&#xff0c;按次序从 0 到 n - 1 进行编号。我们已知每一对相邻公交站之间的距离&#xff0c;distance[i] 表示编号为 i …

Python燃烧废气排放推断算法模型

&#x1f3af;要点 宏观能耗场景模型参数化输入数据&#xff0c;分析可视化输出结果&#xff0c;使用场景时间序列数据模型及定量和定性指标使用线图和箱线图、饼图、散点图、堆积条形图、桑基图等可视化模型输出结果根据气体排放过程得出其时间序列关系&#xff0c;使用推断模…

nginx基础篇(一)

文章目录 学习链接概图一、Nginx简介1.1 背景介绍名词解释 1.2 常见服务器对比IISTomcatApacheLighttpd其他的服务器 1.3 Nginx的优点(1)速度更快、并发更高(2)配置简单&#xff0c;扩展性强(3)高可靠性(4)热部署(5)成本低、BSD许可证 1.4 Nginx的功能特性及常用功能基本HTTP服…

GlusterFS 分布式文件系统

一、GlusterFS 概述 1.1 什么是GlusterFS GlusterFS 是一个开源的分布式文件系统&#xff0c;它可以将多个存储服务器结合在一起&#xff0c;创建一个大的存储池&#xff0c;供客户端使用。它不需要单独的元数据服务器&#xff0c;这样可以提高系统的性能和可靠性。由于没有…