高仿ios斗鱼界面

时间:2022-04-27
本文章向大家介绍高仿ios斗鱼界面,主要内容包括前言、Swift3.0的蝶变、斗鱼部分代码分析、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

前言

过去的2016年是一个直播年,各大平台都相继接入了直播频道,电商,社交…更是火了一批做视频的,譬如喵播,映客,都斗鱼等直播平台。全民直播,一下子掀起了直播的技术潮,今天要聊聊如何实现一个ios的直播app。 首先来看最终的效果:

最近也是因为入门swift不久,在网上找了一个项目就开始模仿,本项目用到的第三方库: Alamofire Kingfisher

Swift3.0的蝶变

swift3.0相对于2.x,渐渐的脱离了oc和c的风格,不管是从命名规范还是新能上都有了较大的提升,笔者认为应该是未来一个相对稳定的版本,而不是1.0和2.0时代的实验版本。相对于2.x,我们来看3.0或以后的3.x主要有哪些特性: 稳定二进制接口(ABI) API大家都知道是应用程序接口 API只是提供函数签名,而ABI是系统和语言层面的 如果ABI稳定 意味着以后Swift版本更新升级 我们不需要再修改老版本 Swift 语言编译的库了。 弹性/韧性 解决易碎二进制接口问题 Fragile binary interface problem是面向对象编程语言的通病 如果在程序中引入了外部库 我们的的程序中使用并继承了该外部库中的类 如果外部库有改动 我们必须重新编译所有该类的继承树 而这类问题被称为脆弱的基类 (Fragile base class) 可移植性 这个对于高级语言是很重要的特性,这意味着Swift可被移植到其他平台上。 全面支持泛型特性 Swift 2.2已经很好的支持泛型 但是还不够完善,Swift 3.0开始 将全面支持泛型的所有特性。 新的API设计规范 Swift3.0 发布了新的语言设计规范 其中在Swift3.0中标准库和核心库将会遵循这个设计规范。规范地址 从函数参数中删除var关键字

func doSomethingWithVar(var i: Int) {
     i = 2 // This will NOT have an effect on the caller's Int that was passed, but i can be modified locally
  }

func doSomethingWithInout(inout i: Int) {
       i = 2 // This will have an effect on the caller's Int that was passed.
}  

doSomethingWithVar(x)  
 print(x) // 1

doSomethingWithInout(&x)
 print(x) // 2

删除var是因为var与inout会产生歧义和混乱。 为autoreleasepool添加错误处理 旧版autoreleasepool处理错误方式:

func doWork() throws -> Result {
   var result: Result? = nil
   var error: ErrorProtocol? = nil
   autoreleasepool { 
          do {
            ... actual computation which hopefully assigns to result but might not ...
         } catch let e {
                       error = e
          }
     } 

    guard let result = result else { 
              throw error! 
      } 
          return result!
  }

Swift3.0 autoreleasepool 处理错误方式:

public func autoreleasepool<Result>(@noescape body: () throws -> Result) rethrows -> Result

  func doWork() throws -> Result {

     return try autoreleasepool
         { 
                 ... actual computation which either returns or throws       ...         
         }
}

允许直接引用(Default, Private, Repeat)关键字成员 在Swift3.0之前我们引用default和repeat成员时 需要这样写:

let cell = UITableViewCell(style: .`default`, reuseIdentifier: nil)
particleSystem.imageSequenceAnimationMode = SCNParticleImageSequenceAnimationMode.`repeat`

Swift3.0时 允许我们直接访问default repeat 关键字成员:

let cell = UITableViewCell(style: .default, reuseIdentifier: nil)
particleSystem.imageSequenceAnimationMode = SCNParticleImageSequenceAnimationMode.repeat

将声明式@noescape和@autoclosure 改为类型属性

func f(@noescape fn : () -> ()) {} // declaration  attribute 

//新的语法
func f(fn : @noescape () -> ()) {} // type attribute.
func f2(a : @autoclosure () -> ()) {} // type attribute.

重命名 Debug 标示符 Debug 标示符重命名后将会与#available #selector 关键字统一风格。

__FILE__ ->  #file
__LINE__ -> #line
__COLUMN__ -> #column
__FUNCTION__ -> #function
__DSO_HANDLE__ -> #dsohandle

斗鱼部分代码分析

本app采用的是mvvm的开发架构,做到业务,数据,页面的真正分离,我们来看几个核心的类: base

import UIKit  

private let kItemMargin : CGFloat = 10  
private let kHeaderViewH : CGFloat = 50  
private let NormalCellID = "NormalCellID"  
private let HeaderViewID = "HeaderViewID"  
let kNormalItemW = (kScreenW - 33 * kItemMargin) / 2  
let kNormalItemH = kNormalItemW * 3 / 4  
let kPrettyItemH = kNormalItemW * 5 / 4  
let PrettyCellID = "PrettyCellID"  

class BaseAnchorVC: BaseVC {  

    //!表示用到的时候保证有值  
    var baseVM : BaseVM!  

    lazy var collectionView : UICollectionView = {[unowned self] in  
        let layout = UICollectionViewFlowLayout()  
        layout.itemSize = CGSize(width: kNormalItemW, height: kNormalItemH)  
        layout.minimumLineSpacing = 0  
        layout.minimumInteritemSpacing = kItemMargin  
        layout.headerReferenceSize = CGSize(width: kScreenW, height: kHeaderViewH)  
        layout.sectionInset = UIEdgeInsets(top: 0, left: kItemMargin, bottom: 0, right: kItemMargin)  

        let collectionView = UICollectionView(frame: self.view.bounds, collectionViewLayout: layout)  
        collectionView.backgroundColor = UIColor.white  
        collectionView.dataSource = self  
        collectionView.delegate = self  
        collectionView.autoresizingMask = [.flexibleHeight, .flexibleWidth]  

        collectionView.register(UINib(nibName: "CollectionNormalCell", bundle: nil), forCellWithReuseIdentifier: NormalCellID)  
        collectionView.register(UINib(nibName: "CollectionPrettyCell", bundle: nil), forCellWithReuseIdentifier: PrettyCellID)  
        collectionView.register(UINib(nibName: "CollectionHeaderView", bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: HeaderViewID)  

        return collectionView  
        }()  

    override func viewDidLoad() {  
        super.viewDidLoad()  
        setupUI()  
        loadData()  
    }  

}  

extension BaseAnchorVC {  
    override func setupUI() {  
        contentView = collectionView  
        view.addSubview(collectionView)  
        super.setupUI()  
    }  
}  

extension BaseAnchorVC {  
    func loadData() {  
    }  
}  

extension BaseAnchorVC : UICollectionViewDataSource {  
    func numberOfSections(in collectionView: UICollectionView) -> Int {  
        return baseVM.anchorGroups.count  
    }  

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {  
        return baseVM.anchorGroups[section].anchors.count  
    }  

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {  
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NormalCellID, for: indexPath) as! CollectionNormalCell  
        cell.anchor = baseVM.anchorGroups[indexPath.section].anchors[indexPath.item]  
        return cell  
    }  

    func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {  
        let headerView = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: HeaderViewID, for: indexPath) as! CollectionHeaderView  
        headerView.group = baseVM.anchorGroups[indexPath.section]  
        return headerView  
    }  

}  

extension BaseAnchorVC : UICollectionViewDelegate {  
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {  
        let anchor = baseVM.anchorGroups[indexPath.section].anchors[indexPath.item]  
        anchor.isVertical == 0 ? pushNormalRoomVc(anchor) : presentShowRoomVc(anchor)  
    }  

    private func presentShowRoomVc(_ anchor : AnchorModel) {  
        let showVc = ShowRoomVC()  
        showVc.anchor = anchor  
        present(showVc, animated: true, completion: nil)  
    }  

    private func pushNormalRoomVc(_ anchor : AnchorModel) {  
        let normalVc = NormalRoomVC()  
        normalVc.anchor = anchor  
        navigationController?.pushViewController(normalVc, animated: true)  
    }  
} 
import UIKit  

class GameVC: BaseAnchorVC {  
    fileprivate lazy var gameVM : GameVM = GameVM()  
    fileprivate lazy var menuView : MenuView = {  
        let menuView = MenuView.menuView()  
        menuView.frame = CGRect(x: 0, y: -kMenuViewH, width: kScreenW, height: kMenuViewH)//设置collectionView的-y,放置menuView  
        return menuView  
    }()  

}  

extension GameVC {  
    override func setupUI() {  
        super.setupUI()  
        collectionView.addSubview(menuView)  
        collectionView.contentInset = UIEdgeInsets(top: kMenuViewH, left: 0, bottom: 0, right: 0)//设置内边距  
    }  
}  

extension GameVC{  
    override func loadData() {  
        baseVM = self.gameVM  
        gameVM.requestData {  
            self.collectionView.reloadData()  
            var gameGroups = Array(self.gameVM.anchorGroups[1...15])//0...15 & gameGroups.removeFirst()  
            let moreGroup = AnchorGroup()  
            moreGroup.tag_name = "更多分类"  
            gameGroups.append(moreGroup)  
            self.menuView.groups = gameGroups  
            self.loadDataFinished()  
        }  
    }  
}  

请求类:

import UIKit  
import Alamofire  

enum MethodType {  
    case get  
    case post  
}  

class HttpTools {  
    class func requestData(_ type : MethodType, URLString : String, parameters : [String : Any]? = nil, finishedCallback :  @escaping (_ result : Any) -> ()) {  
        let method = type == .get ? HTTPMethod.get : HTTPMethod.post  
        Alamofire.request(URLString, method: method, parameters: parameters).responseJSON { (response) in  
            guard let result = response.result.value else {  
                print(response.result.error)  
                return  
            }  
            finishedCallback(result)  
        }  
    }  
} 

附:swift斗鱼app界面 oc代码原文