IOS 25 实现歌单详情(UITableView)列表 ②

歌单详情完整效果

歌单详情歌单列表头部+图片背景效果

本节是在文章 IOS 24 实现歌单详情(UITableView)列表 的基础上,实现歌单详情里面的歌单列表头部Cell和图片背景效果。

歌单列表头部Cell实现

实现流程:

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

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

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

1)创建和注册Cell

从效果图上面可以看出,歌单列表头部Cell由一个垂直方向布局包含歌单信息和快捷按钮两部分来实现。

自定义SheetInfoCell,继承自BaseTableViewCell,设置TGLinearLayout为垂直方向。

Swift 复制代码
class SheetInfoCell: BaseTableViewCell{
    
    static let NAME = "SheetInfoCell"
    
    override func initViews() {
        super.initViews()
        container.tg_padding = UIEdgeInsets(top: PADDING_OUTER, left: PADDING_OUTER, bottom: PADDING_LARGE2, right: PADDING_OUTER)
        container.tg_space = PADDING_LARGE2
        
    }
    
    override func getContainerOrientation() -> TGOrientation {
        return .vert
    }
}

添加歌单信息和快捷按钮布局,并绑定布局数据

Swift 复制代码
class SheetInfoCell: BaseTableViewCell{
    
    static let NAME = "SheetInfoCell"
    
    override func initViews() {
        super.initViews()
        container.tg_padding = UIEdgeInsets(top: PADDING_OUTER, left: PADDING_OUTER, bottom: PADDING_LARGE2, right: PADDING_OUTER)
        container.tg_space = PADDING_LARGE2
        
        //水平容器
        let orientationContainer = ViewFactoryUtil.orientationContainer()
        orientationContainer.tg_space = PADDING_OUTER
        orientationContainer.tg_gravity = TGGravity.vert.center
        orientationContainer.tg_padding = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: PADDING_SMALL)
        container.addSubview(orientationContainer)
        
        //图标
        orientationContainer.addSubview(iconView)
        
        //右侧容器
        let rightContainer = TGLinearLayout(.vert)
        rightContainer.tg_width.equal(.fill)
        rightContainer.tg_height.equal(.wrap)
        rightContainer.tg_space = PADDING_SMALL
        orientationContainer.addSubview(rightContainer)
        
        //标题
        rightContainer.addSubview(titleView)
        
        //用户容器
        let userContainer = ViewFactoryUtil.orientationContainer()
        userContainer.tg_space = PADDING_SMALL
        userContainer.tg_gravity = TGGravity.vert.center
        rightContainer.addSubview(userContainer)
        
        userContainer.addSubview(avatarView)
        userContainer.addSubview(nicknameView)
        
        //详情容器
        let detailContainer = ViewFactoryUtil.orientationContainer()
        detailContainer.tg_top.equal(PADDING_MEDDLE)
        detailContainer.tg_space = PADDING_SMALL
        userContainer.tg_gravity = TGGravity.vert.center
        rightContainer.addSubview(detailContainer)
        
        detailContainer.addSubview(detailView)
        detailContainer.addSubview(ViewFactoryUtil.moreIconView())
        
        //快捷按钮容器
        let buttonContainer = ViewFactoryUtil.orientationContainer()
        buttonContainer.corner(23)
        buttonContainer.tg_horzMargin(PADDING_LARGE2)
        buttonContainer.tg_height.equal(46)
        container.addSubview(buttonContainer)
        
        buttonContainer.addSubview(collectCountView)
        buttonContainer.addSubview(ViewFactoryUtil.smallVerticalDivider())
        buttonContainer.addSubview(commentCountView)
        buttonContainer.addSubview(ViewFactoryUtil.smallVerticalDivider())
        buttonContainer.addSubview(shareCountView)
    }
    
    func bind(_ data:Sheet) {
        iconView.show2(data.icon)
        titleView.text = data.title
        avatarView.show2(data.user.icon)
        nicknameView.text = data.user.nickname
        detailView.text = data.detail
        
        collectCountView.setTitle("\(data.collectsCount)", for: .normal)
        commentCountView.setTitle("\(data.commentsCount)", for: .normal)
    }
    
    override func getContainerOrientation() -> TGOrientation {
        return .vert
    }
    
    lazy var iconView: UIImageView = {
        let r = UIImageView()
        r.tg_width.equal(120)
        r.tg_height.equal(120)
        r.image = R.image.placeholder()
        r.smallCorner()
        r.contentMode = .scaleAspectFill
        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_LARGE2)
        r.textColor = .colorLightWhite
        return r
    }()
    
    lazy var avatarView: UIImageView = {
        let r = UIImageView()
        r.tg_width.equal(30)
        r.tg_height.equal(30)
        r.contentMode = .scaleAspectFill
        r.smallCorner()
        return r
    }()
    
    lazy var nicknameView: UILabel = {
        let r = UILabel()
        r.tg_width.equal(.wrap)
        r.tg_height.equal(.wrap)
        r.numberOfLines = 1
        r.font = UIFont.systemFont(ofSize: TEXT_MEDDLE)
        r.textColor = .colorLightWhite
        return r
    }()
    
    lazy var detailView: UILabel = {
        let r = UILabel()
        r.tg_width.equal(160)
        r.tg_height.equal(.wrap)
        r.numberOfLines = 1
        r.font = UIFont.systemFont(ofSize: TEXT_MEDDLE)
        r.textColor = .colorLightWhite
        return r
    }()
    
    lazy var collectCountView: QMUIButton = {
        let r = ViewFactoryUtil.secoundButton(icon: R.image.search()!.withTintColor(), title: "0")
        r.backgroundColor = .colorLightWhite
        return r
    }()
    
    lazy var commentCountView: QMUIButton = {
        let r = ViewFactoryUtil.secoundButton(icon: R.image.search()!.withTintColor(), title: "0")
        r.backgroundColor = .colorLightWhite
        return r
    }()
    
    lazy var shareCountView: QMUIButton = {
        let r = ViewFactoryUtil.secoundButton(icon: R.image.search()!.withTintColor(), title: "0")
        r.backgroundColor = .colorLightWhite
        return r
    }()
}

在SheetDetailController控制器,注册SheetInfoCell

Swift 复制代码
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)
        
        tableView.bounces = false
    }
}
2)获取data列表数据

定义列表数据模型Sheet

Swift 复制代码
//
//  Sheet.swift
//  歌单对象
//
//  Created by jin on 2024/8/23.
//

import Foundation

//导入JSON解析框架
import HandyJSON

class Sheet:BaseCommon {
    /// 歌单标题
    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?
    
    override func mapping(mapper: HelpingMapper) {
        super.mapping(mapper: mapper)
        mapper <<< self.clicksCount <-- "clicks_count"
        mapper <<< self.collectsCount <-- "collects_count"
        mapper <<< self.commentsCount <-- "comments_ount"
        mapper <<< self.songsCount <-- "songs_count"
    }
}

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

Swift 复制代码
class SheetDetailController: BaseTitleController {
    /// 数据id
    var id: String!
    var data: Sheet!
    
    override func initViews() {
        super.initViews()
        // 注册单曲
        tableView.register(SongItemCell.self, forCellReuseIdentifier: Constant.CELL)
        tableView.register(SheetInfoCell.self, forCellReuseIdentifier: SheetInfoCell.NAME)
        
        tableView.bounces = false
    }
    
    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
        
        //第一组
        datum = [data]
        
        //第二组
        if let r = data.songs {
            if !r.isEmpty {
                //有音乐才设置

                //设置数据
                datum += data.songs ?? []
                superFooterContainer.backgroundColor = .colorLightWhite
            }
        }
        
        tableView.reloadData()
    }

    /// 获取列表类型
    func typeForItemAtData(_ data:Any) -> MyStyle {
        if data is Sheet {
            return .sheet
        }
        
        return .song
    }
    
}
3)Item数据绑定Cell

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

Swift 复制代码
extension SheetDetailController {
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let data = 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)"
            
            return cell
        }
    }
}

歌单详情图片背景效果

为整个界面添加图片背景和模糊效果

Swift 复制代码
class SheetDetailController: BaseTitleController {

    //背景
    var backgroundImageView: UIImageView!
    
    //背景模糊
    var backgroundVisual: UIVisualEffectView!
    
    override func initViews() {
        super.initViews()
        
        //添加背景图片控件
        backgroundImageView = UIImageView()
        backgroundImageView.clipsToBounds = true
        backgroundImageView.alpha = 0
        backgroundImageView.contentMode = .scaleAspectFill
        view.addSubview(backgroundImageView)
        
        //背景模糊效果
        let blur = UIBlurEffect(style: .dark)
        backgroundVisual = UIVisualEffectView(effect: blur)
        backgroundImageView.addSubview(backgroundVisual)
        
        // 初始化TableView结构
        initTableViewSafeArea()
        
        title = R.string.localizable.sheet()
        
        // 注册单曲
        tableView.register(SongItemCell.self, forCellReuseIdentifier: Constant.CELL)
        tableView.register(SheetInfoCell.self, forCellReuseIdentifier: SheetInfoCell.NAME)
        
        tableView.bounces = false
    }
}

从歌单详情接口中获取歌单封面图片作为界面的背景图片

Swift 复制代码
class SheetDetailController: BaseTitleController {
    /// 数据id
    var id: String!
    var data: Sheet!
    
    //背景
    var backgroundImageView: UIImageView!
    
    //背景模糊
    var backgroundVisual: UIVisualEffectView!
    
    override func initViews() {
        super.initViews()
        
        //添加背景图片控件
        backgroundImageView = UIImageView()
        backgroundImageView.clipsToBounds = true
        backgroundImageView.alpha = 0
        backgroundImageView.contentMode = .scaleAspectFill
        view.addSubview(backgroundImageView)
        
        //背景模糊效果
        let blur = UIBlurEffect(style: .dark)
        backgroundVisual = UIVisualEffectView(effect: blur)
        backgroundImageView.addSubview(backgroundVisual)
        
    }
    
    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
        
        backgroundImageView.show(data.icon)
        
        //使用动画显示背景图片
        UIView.animate(withDuration: 0.3) {
            //透明度设置为1
            self.backgroundImageView.alpha = 1
        }
        
    }
    
}

在viewDidLayoutSubviews()方法里面设置背景图片和模糊效果的宽高和坐标

Swift 复制代码
class SheetDetailController: BaseTitleController {
    /// 数据id
    var id: String!
    var data: Sheet!
    
    //背景
    var backgroundImageView: UIImageView!
    
    //背景模糊
    var backgroundVisual: UIVisualEffectView!
    
    override func initViews() {
        super.initViews()
        
        //添加背景图片控件
        backgroundImageView = UIImageView()
        backgroundImageView.clipsToBounds = true
        backgroundImageView.alpha = 0
        backgroundImageView.contentMode = .scaleAspectFill
        view.addSubview(backgroundImageView)
        
        //背景模糊效果
        let blur = UIBlurEffect(style: .dark)
        backgroundVisual = UIVisualEffectView(effect: blur)
        backgroundImageView.addSubview(backgroundVisual)

    }
    
    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
        
        backgroundImageView.show(data.icon)
        
        //使用动画显示背景图片
        UIView.animate(withDuration: 0.3) {
            //透明度设置为1
            self.backgroundImageView.alpha = 1
        }
        
    }
    
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        //设置背景宽高+坐标
        backgroundImageView.frame = view.bounds
        backgroundVisual.frame = backgroundImageView.bounds
    }
}

至此,实现了歌单详情里面的歌单列表头部+图片背景效果。

相关推荐
Unlimitedz13 小时前
iOS内存管理中的强引用问题
macos·ios·cocoa
雨夜赶路人13 小时前
iOS开发--接入ADMob广告失败
ios
旭日猎鹰15 小时前
iOS崩溃堆栈分析
ios
SY.ZHOU15 小时前
Flutter 与原生通信
android·flutter·ios
鸿蒙布道师18 小时前
鸿蒙NEXT开发文件预览工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师18 小时前
鸿蒙NEXT开发全局上下文管理类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
归辞...19 小时前
【iOS】OC高级编程 iOS多线程与内存管理阅读笔记——自动引用计数(二)
笔记·ios·cocoa
码客前端20 小时前
ios接入穿山甲【Swift】
macos·ios·cocoa
键盘敲没电20 小时前
【iOS】UITableView性能优化
ios·性能优化·ipad
星鹿XINGLOO20 小时前
ChatGPT语音功能在iPad上支持吗?全面解答!
人工智能·安全·ios·ai·chatgpt·语音识别·ipad