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

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

相关推荐
linweidong7 小时前
唯品会ios开发面试题及参考答案
ios开发·ios面试·uitableview·nstimer·ios进程·ios线程·swift开发
专业开发者11 小时前
调试 iOS 蓝牙应用的新方法
物联网·macos·ios·cocoa
tangbin58308515 小时前
iOS Swift 可选值(Optional)详解
前端·ios
卷心菜加农炮1 天前
基于Python的FastAPI后端开发框架如何使用PyInstaller 进行打包与部署
ios
北极象2 天前
千问大模型接入示例
ios·iphone·qwen
ipad协议开发2 天前
企业微信 iPad 协议应用机器人开发
ios·企业微信·ipad
QuantumLeap丶2 天前
《Flutter全栈开发实战指南:从零到高级》- 26 -持续集成与部署
android·flutter·ios
2501_915918412 天前
TCP 抓包分析在复杂网络问题中的作用,从连接和数据流层面理解系统异常行为
网络·网络协议·tcp/ip·ios·小程序·uni-app·iphone
二流小码农3 天前
鸿蒙开发:个人开发者如何使用华为账号登录
android·ios·harmonyos
wvy3 天前
Xcode 26还没有适配SceneDelegate的app建议尽早适配
ios