IOS 20 发现界面(UITableView)歌单列表(UICollectionView)实现

news2024/9/23 13:30:46

发现界面完整效果

本文实现歌单列表效果

文章基于 IOS 19 发现界面(UITableView)快捷按钮实现 继续实现发现界面歌单列表效果

歌单列表Cell实现

实现流程:

1.创建Cell,及在使用UITableView的Controller控制器上注册Cell;

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

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

1)创建和注册Cell

从效果图上面可以看出,歌单列表Cell由一个title + 宫格列表View来实现的,这里宫格列表使用UICollectionView来实现。需要注意的是,UICollectionView本身也是一个可滑动的控件,把UICollectionView内嵌到UITableView中,且需要显示UICollectionView的全部Item;那么就需要设置UICollectionView不允许滑动,且UICollectionView高度 == Item高度*行数。

通过懒加载创建ItemTitleView 和 UICollectionView,自定义ItemTitleView,就是TGRelativeLayout包含title和右边的icon,并封装UICollectionView到ViewFactoryUtil.collectionView()中。

    /// 标题控件
    lazy var titleView: ItemTitleView = {
        let r = ItemTitleView()
        r.titleView.text = R.string.localizable.recommendSheet()
        return r
    }()
    
    lazy var collectionView: UICollectionView = {
        let r = ViewFactoryUtil.collectionView()
        r.delegate = self
        r.dataSource = self
        r.isScrollEnabled = false
        return r
    }()
//
//  ItemTitleView.swift
//  首页-发现界面-歌单组/推荐单曲组 标题view
//
//  Created by jin on 2024/8/29.
//

import UIKit

import TangramKit

class ItemTitleView : TGRelativeLayout{
    
    init(){
        super.init(frame: CGRect.zero)
        initViews()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        initViews()
    }
    
    func initViews(){
        tg_width.equal(.fill)
        tg_height.equal(.wrap)
        tg_padding = UIEdgeInsets(top: PADDING_MEDDLE, left: PADDING_OUTER, bottom: PADDING_MEDDLE, right: PADDING_OUTER)
        
        addSubview(titleView)
        addSubview(moreIconView)
    }
    
    lazy var titleView: UILabel = {
        let r = UILabel()
        r.tg_width.equal(.wrap)
        r.tg_height.equal(.wrap)
        r.tg_centerY.equal(0)
        r.numberOfLines = 1
        r.font = UIFont.boldSystemFont(ofSize: TEXT_LARGE2)
        r.textColor = .colorOnSurface
        return r
    }()
    
    lazy var moreIconView: UIImageView = {
        let r = UIImageView()
        r.tg_width.equal(15)
        r.tg_height.equal(15)
        r.image = R.image.superChevronRight()?.withTintColor()
        r.tintColor = .black80
        r.tg_right.equal(0)
        r.tg_centerY.equal(0)
        
        //图片完全显示到控件里面
        r.contentMode = .scaleAspectFit
        return r
    }()
}
    /// 创建CollectionView
    static func collectionView() -> UICollectionView {
        let r = UICollectionView(frame: CGRect.zero, collectionViewLayout: collectionViewFlowLayout())
        r.backgroundColor = .clear
        
        //不显示滚动条
        r.showsVerticalScrollIndicator = false
        r.showsHorizontalScrollIndicator = false
        
        //collectionView的内容从collectionView顶部距离开始显示,不要自动偏移状态栏尺寸
        r.contentInsetAdjustmentBehavior = .never
        
        r.tg_width.equal(.fill)
        r.tg_height.equal(.fill)
        
        return r
    }

重写,添加ItemTitleView 和 UICollectionView到SheetGroupCell

class SheetGroupCell:BaseTableViewCell{
    
    static let NAME = "SheetGroupCell"
    
    var datum:Array<Sheet> = []
    
    override func initViews() {
        super.initViews()
        
        //分割线
        container.addSubview(ViewFactoryUtil.smallDivider())
        
        //标题
        container.addSubview(titleView)
        
        container.addSubview(collectionView)
    }

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

绑定宫格列表数据,动态计算Item宽高。

    func bind(_ data:SheetData) {
        //计算每个cell宽度
        
        //屏幕宽度-外边距16*2-(self.spanCount-1)*5
        cellWidth = (SCREEN_WIDTH-PADDING_OUTER*CGFloat(2) - (spanCount - CGFloat(1))*PADDING_SMALL)/spanCount
        
        //cell高度,5:图片和标题边距,40:2行文字高度
        cellHeight = cellWidth + PADDING_SMALL + 40
        
        //计算可以显示几行
        let rows = ceil(CGFloat(data.datum.count) / spanCount)
        
        //CollectionView高度等于,行数*行高,10:垂直方向每个cell间距
        let viewHeight = rows * (cellHeight + PADDING_MEDDLE)
        
        collectionView.tg_height.equal(viewHeight)
        
        datum.removeAll()
        datum = data.datum
        collectionView.reloadData()
    }

注册SheetGroupCell

class DiscoveryController: BaseLogicController {

    override func initViews() {
        super.initViews()
        setBackgroundColor(.colorBackgroundLight)
        
        //初始化TableView结构
        initTableViewSafeArea()
        
        //注册cell
        tableView.register(BannerCell.self, forCellReuseIdentifier: Constant.CELL)
        tableView.register(ButtonCell.self, forCellReuseIdentifier: ButtonCell.NAME)
        tableView.register(SheetGroupCell.self, forCellReuseIdentifier: SheetGroupCell.NAME)
        
    }
}

2)获取data列表数据

定义宫格列表数据模型SheetData

//
//  SheetData.swift
//  发现界面歌单数据
//
//  Created by jin on 2024/8/29.
//

import Foundation

class SheetData{
    var datum:[Sheet]!
    
    init(_ datum: [Sheet]!) {
        self.datum = datum
    }
}

请求接口获取歌单列表数据,更新tableView.reloadData()

class DiscoveryController: BaseLogicController {

    override func initViews() {
        super.initViews()
        setBackgroundColor(.colorBackgroundLight)
        
        //初始化TableView结构
        initTableViewSafeArea()
        
        //注册cell
        tableView.register(BannerCell.self, forCellReuseIdentifier: Constant.CELL)
        tableView.register(ButtonCell.self, forCellReuseIdentifier: ButtonCell.NAME)
        tableView.register(SheetGroupCell.self, forCellReuseIdentifier: SheetGroupCell.NAME)
    }
    
    override func initDatum() {
        super.initDatum()
        loadData()
    }
    
    func loadData() {
        DefaultRepository.shared.bannerAds().subscribeSuccess { [weak self] data in
            //清除原来的数据
            self?.datum.removeAll()
            
            //添加轮播图
            self?.datum.append(BannerData(data:data.data!.data!))
            
            //添加快捷按钮
            self?.datum.append(ButtonData())
            
            //请求歌单数据
            self?.loadSheetsData()
        }.disposed(by: rx.disposeBag)
    }
    
    /// 请求歌单数据
    func loadSheetsData() {
        DefaultRepository.shared.sheets(size: VALUE12).subscribeSuccess { [weak self] data in
            //添加歌单数据
            self?.datum.append(SheetData(data.data!.data!))
            
            self?.tableView.reloadData()
        }.disposed(by: rx.disposeBag)
    }
}

3)Item数据绑定Cell

DiscoveryController控制器重写父类的扩展 cellForRowAt方法,创建对应的Cell,并将Item数据绑定到Cell。

extension DiscoveryController{
    
    // 返回当前位置cell
    /// - Parameters:
    ///   - tableView: <#tableView description#>
    ///   - indexPath: <#indexPath description#>
    /// - Returns: <#description#>
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let data = datum[indexPath.row]
        
        //获取当前Cell的类型
        let type = typeForItemAtData(data)
        
        switch(type){
        case .button:
            //按钮
            let cell = tableView.dequeueReusableCell(withIdentifier:  ButtonCell.NAME, for: indexPath) as! ButtonCell
            cell.bind(data as! ButtonData)
            return cell
        case .sheet:
            //歌单
            let cell = tableView.dequeueReusableCell(withIdentifier:  SheetGroupCell.NAME, for: indexPath) as! SheetGroupCell
            cell.bind(data as! SheetData)
            return cell
        default:
            //banner
            
            //取出一个Cell
            let cell = tableView.dequeueReusableCell(withIdentifier:  Constant.CELL, for: indexPath) as! BannerCell
            
            //绑定数据
            cell.bind(data as! BannerData)
            
            cell.bannerClick = {[weak self] data in
                    print("bannerClick \(data)")
            }

            return cell
        }
    }
}

歌单UICollectionView Cell实现

由于UICollectionView也是一个列表控件,实现UICollectionView的Cell的流程跟 实现UITableView的Cell的流程基本类似。

实现流程:

1.创建Cell,及在使用UICollectionView的View上注册Cell;

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

3.将data的Item数据绑定UICollectionView的每一个Cell;

4.重写UICollectionViewDelegateFlowLayout,计算Cell的尺寸。

1)创建和注册Cell

从效果图上面可以看出,歌单列表Item的Cell由一个垂直TGLinearLayout包含UIImageView + UILabel来实现的。布局比较简单,实现代码如下:

//
//  SheetCell.swift
//  歌单cell
//
//  Created by jin on 2024/8/29.
//

import Foundation

class SheetCell : BaseCollectionViewCell{
    
    override func initViews() {
        super.initViews()
        
        container.tg_space = PADDING_SMALL
        
        container.addSubview(iconView)
        container.addSubview(titleView)
    }
    
    func bind(_ data:Sheet) {
        //OC库显示图片
//        if let r = data.icon {
//            let r = ResourceUtil.resourceUri(r)
//            iconView.sd_setImage(with: URL(string: r), placeholderImage: R.image.placeholder())
//        }
        
        iconView.show(data.icon)
        
        titleView.text = data.title
    }
    
    lazy var iconView: UIImageView = {
        let r = UIImageView()
        r.tg_width.equal(.fill)
        r.tg_height.equal(r.tg_width)
        r.image = R.image.placeholder()
        
        //图片从中心等比向外面填充,控件没有黑边,但图片可能被裁剪
        r.contentMode = .scaleAspectFill
        
        //小圆角
        r.smallCorner()
        return r
    }()
    
    /// 标题
    lazy var titleView: UILabel = {
        let r = UILabel()
        r.tg_width.equal(.fill)
        r.tg_height.equal(.wrap)
        r.numberOfLines = 2
        r.font = UIFont.systemFont(ofSize:TEXT_MEDDLE)
        r.textColor = .colorOnSurface
        return r
    }()
}

 2)获取data列表数据

定义宫格列表Item数据模型Sheet

//
//  Sheet.swift
//  歌单对象
//
//  Created by jin on 2024/8/23.
//

import Foundation

//导入JSON解析框架
import HandyJSON

class Sheet : HandyJSON{
    
    /// 歌单标题
    var title:String!

    /// 歌单封面
    var icon:String?

    /// 点击数
    var clicksCount:Int=0

    /// 收藏数
    var collectsCount:Int=0

    /// 评论数
    var commentsCount:Int=0

    /// 音乐数量
    var songsCount:Int=0

    /// 歌单创建者
//    var user:User!
//
//    /// 歌曲列表
//    var songs:Array<Song>?
    
    var detail:String?
    
    required init() {}
}

从SheetGroupCell bind()中获取歌单宫格列表Item数据,更新collectionView.reloadData()

class SheetGroupCell:BaseTableViewCell{
    
    static let NAME = "SheetGroupCell"
    
    var datum:Array<Sheet> = []
    
    var cellWidth:CGFloat!
    var cellHeight:CGFloat!
    var spanCount:CGFloat = 3
    
    override func initViews() {
        super.initViews()
        
        //分割线
        container.addSubview(ViewFactoryUtil.smallDivider())
        
        //标题
        container.addSubview(titleView)
        
        container.addSubview(collectionView)
        
        collectionView.register(SheetCell.self, forCellWithReuseIdentifier: Constant.CELL)
    }
    
    override func getContainerOrientation() -> TGOrientation {
        return .vert
    }
    
    func bind(_ data:SheetData) {
        //计算每个cell宽度
        
        //屏幕宽度-外边距16*2-(self.spanCount-1)*5
        cellWidth = (SCREEN_WIDTH-PADDING_OUTER*CGFloat(2) - (spanCount - CGFloat(1))*PADDING_SMALL)/spanCount
        
        //cell高度,5:图片和标题边距,40:2行文字高度
        cellHeight = cellWidth + PADDING_SMALL + 40
        
        //计算可以显示几行
        let rows = ceil(CGFloat(data.datum.count) / spanCount)
        
        //CollectionView高度等于,行数*行高,10:垂直方向每个cell间距
        let viewHeight = rows * (cellHeight + PADDING_MEDDLE)
        
        collectionView.tg_height.equal(viewHeight)
        
        datum.removeAll()
        datum = data.datum
        collectionView.reloadData()
    }
}

3)Item数据绑定Cell

SheetGroupCell 重写父类的扩展 cellForItemAt方法,创建对应的Cell,并将Item数据绑定到Cell。

/// CollectionView数据源和代理
extension SheetGroupCell:UICollectionViewDataSource,UICollectionViewDelegate{
    
    /// 有多少个
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return datum.count
    }
    
    /// 返回cell
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let data = datum[indexPath.row]
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: Constant.CELL, for: indexPath) as! SheetCell
        cell.bind(data)
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        if let d = delegate {
            d.sheetCLick(data: datum[indexPath.row])
        }
    }
}

4)计算Cell尺寸

重写UICollectionViewDelegateFlowLayout,返回Cell的尺寸

/// UICollectionViewDelegateFlowLayout
extension SheetGroupCell:UICollectionViewDelegateFlowLayout{
    /// 返回CollectionView里面的Cell到CollectionView的间距
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, insetForSectionAt section: Int) -> UIEdgeInsets {
        return UIEdgeInsets(top: 0, left: PADDING_OUTER, bottom: PADDING_OUTER, right: PADDING_OUTER)
    }
    
    /// 返回每个Cell的行间距
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {
        return PADDING_MEDDLE
    }
    
    /// 返回每个Cell的列间距
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
        return PADDING_SMALL
    }
    
    /// cell尺寸
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: cellWidth, height: cellHeight)
    }
}

至此完成歌单列表的实现。

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

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

相关文章

uni-app应用更新(Android端)

关于app更新&#xff0c;uni-app官方推荐的是 uni-upgrade-center&#xff0c;看了下比较繁琐&#xff0c;因此这里自己实现检查更新并下载安装的逻辑。 1.界面效果 界面中的弹框和 进度条采用了uView 提供的组件 2.检查更新并下载安装 一、版本信息配置在服务端&#xff0c…

【Azure Redis】Redis-CLI连接Redis 6380端口始终遇见 I/O Error

问题描述 使用Redis-cli连接Redis服务&#xff0c;因为工具无法直接支持TLS 6380端口连接&#xff0c;所以需要使用 stunnel 配置TLS/SSL服务。根据文章(Linux VM使用6380端口(SSL方式)连接Azure Redis (redis-cli & stunnel) &#xff1a; https://www.cnblogs.com/luligh…

Python使用turtle画笑脸

import turtle as t t.pensize(5) #设置画笔尺寸 t.color("red","yellow") #设置画笔颜色 t.begin_fill() #开始填充 t.circle(150) #绘制一个半径为100像素的圆 t.end_fill() #结束填充#画眼睛&#xff08;左眼…

Leetcode - 周赛413

目录 一&#xff0c;3274. 检查棋盘方格颜色是否相同 二&#xff0c;3275. 第 K 近障碍物查询 三&#xff0c;3276. 选择矩阵中单元格的最大得分 四&#xff0c;3277. 查询子数组最大异或值 一&#xff0c;3274. 检查棋盘方格颜色是否相同 本题就是找规律&#xff0c;假设白…

x11转发远程图形界面

1、 开一个有vnc的节点 2、 开放所有用户的Xserver权限 xhost 3、X11转发 ssh hlzhang192.168.3.156 -X4、打开远程窗口 paraview在227的界面打开156的图形窗口

uniapp和vue3中使用vConsole在H5中开启移动端调试

uniapp和vue3中使用vConsole在H5中开启移动端调试 1. 安装vconsole npm install vconsole --save2. 在main.js中全局引入 重新启动项目即可

【C++】手搓实现模板类

myTamplate.h #ifndef MYTAMPLATE_H #define MYTAMPLATE_H #include <iostream> using namespace std;template<typename T> class Node {T *data; //数据域int size; //指针域int len;//实际长度 public://无参构造Node():size(10),len(0){data new T[size]…

写的一致性问题之双写模式

文章目录 1、先写mysql&#xff1a;mysql会回滚&#xff0c;而redis不会回滚2、先写redis&#xff1a; 1、先写mysql&#xff1a;mysql会回滚&#xff0c;而redis不会回滚 写入msql成功&#xff0c;写入redis也成功&#xff0c;但是后续事务提交失败&#xff0c;mysql会回滚&a…

Django学习(一)

一、创建django项目 二、修改settings.py里的配置&#xff1a; 1、修改语言和时区&#xff1a; # 语言编码 LANGUAGE_CODE zh-hansTIME_ZONE UTCUSE_I18N True# 不用时区 USE_TZ False 2、配置数据库&#xff1a; DATABASES {default: {ENGINE: django.db.backends.m…

Python中的self有什么作用

你是否曾经好奇过,为什么Python类中的方法总是有一个神秘的self参数?为什么有时它似乎可有可无,有时却又不可或缺?今天,让我们一起深入探讨Python中self的奥秘,揭开面向对象编程的神秘面纱! 目录 引言:self的重要性self的本质:实例的引用为什么需要self?self的工作原理self的…

极米科技:走出舒适圈,推动数据架构现代化升级 | OceanBase 《DB大咖说》

《DB 大咖说》第 13 期&#xff0c;邀请到了极米科技软件与创新产品线高级架构师施刘凡来进行分享。 在小红书平台上&#xff0c;“是否应将家里的电视升级为投影仪&#xff1f;”这一话题激发了上百万篇笔记的分享与推荐&#xff0c;反映出年轻群体对投影仪的偏好。随着手机、…

Java MVC

1. MVC模式 1.1. JavaBean JavaBean&#xff1a;符合特定规范的Java类&#xff0c;是一种可重用的组件 特定规范&#xff1a; public, class, 提供无参数构造方法属性private提供public的getter和setter方法 功能分类&#xff1a; 封装数据&#xff1a;数据Bean&#xff0c…

【gtokentool】什么是数字货币?怎么使用?

一、什么是数字货币 数字货币是一种基于密码学原理&#xff0c;独立于传统银行体系运行的电子货币形式。数字货币具有以下特点&#xff1a; 去中心化&#xff1a;数字货币采用去中心化的交易验证方式&#xff0c;不依赖于任何中央机构或政府。安全性高&#xff1a;通过加密算法…

STM32G474之DAC

STM32G474分别使用CORDIC硬件和“math.h”的正弦值&#xff0c;从DAC1和DAC2输出。 1、DAC特点 PA4的附加功能为DAC1_OUT1&#xff0c;无需映射&#xff0c;直接将它配置为模拟功能&#xff0c;就可以使用了。 PA6的附加功能为DAC2_OUT1&#xff0c;无需映射&#xff0c;直接将…

数据结构-栈、队列-详解

数据结构-栈、队列-详解 1.前言2.栈2.1是什么2.2函数实现struct StackStackInitStackDestroyStackPushStackSizeStackEmptyStackTopStackPop 2.3小结 3.队列3.1是什么3.2函数实现struct QueueQueueInitQueueDestroyQueueEmptyQueuePushQueuePopQueueFrontQueueBackQueueSize 3.…

Verilog基础,原码,反码与补码的概念

Verilog模块初认识 1、Verilog模块(Module) Verilog中的module可以看成一个具有输入输出端口的黑盒子&#xff0c;该黑盒子有输入和输出接口(信号)&#xff0c;通过把输入在盒子中执行某些操作来实现某项功能。(类似于C语言中的函数) 图1 模块示意图 1.1 模块描述 图1 所示的…

【408DS算法题】035进阶-17年真题_二叉树转中缀表达式

Index 真题题目分析实现总结 真题题目 请设计一个算法&#xff0c;将给定的表达式树&#xff08;二叉树&#xff09;转换为等价的中缀表达式&#xff08;通过括号反映操作符的计算次序&#xff09;并输出。 例如&#xff0c; 当下列两棵表达式树作为算法的输入时&#xff0c; …

vivado 定义约束设置和文件

步骤2&#xff1a;定义约束集和文件 首先创建一个新的约束集&#xff0c;并向其中添加一个空的XDC约束文件 示例设计已经包含两个约束集&#xff0c;但您没有在本实验室中使用它们。 1.在“流导航器”中&#xff0c;单击“项目管理器”部分中的“添加源”。 2.从“添加源”对话…

【自考zt】【软件工程】【21.10】

关键字&#xff1a; 软件需求基本性质、软件系统需求挑战、耦合&#xff08;高内容&#xff0c;低无直接&#xff09;、内聚&#xff08;初始化时间&#xff09;、uml包、rup边界类、测试首要目标、单元测试最后工作、性能需求 软件开发本质、软件需求规约三种风格、提炼、用…

windows C++ 并行编程-并发和UWP(二)

下面例程用于演示在UWP中进行并发编程。 示例: 创建 C Windows 运行时组件并从 C# 中使用它 假设有一个使用 XAML 和 C# 的应用&#xff0c;可用它来定义 UI 和 C Windows 运行时组件以执行计算密集型操作。 在此示例中&#xff0c;C 组件会计算给定范围中的哪些数字是质数。…