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为垂直方向。

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

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

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

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

//
//  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()

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。

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

歌单详情图片背景效果

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

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

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

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()方法里面设置背景图片和模糊效果的宽高和坐标

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sziitjin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值